google / json_serializable.dart

Generates utilities to aid in serializing to/from JSON.
https://pub.dev/packages/json_serializable
BSD 3-Clause "New" or "Revised" License
1.54k stars 393 forks source link

json_serializable _$*EnumMap generation #1377

Open ekuleshov opened 8 months ago

ekuleshov commented 8 months ago

When json_serializable is generating code to serialize an enum values it creates a private const map named _$<enum name>EnumMap.

However this map is being generated not for the dart source file where this enum is declared at, but in the sources this enum is referenced in. Basically the same map is generated for every source file that is referencing that same enum type.

This adds some code duplication even when this enum is serialized the same way in all places.

Additionally, if the model where this enum is declared at is coming from a different module (e.g. a common data model or some common API) we have to regenerate all generated code in all modules that depend on the module where enum is coming from.

To address those two issues it would be great if json_serializable could generate a public map or a common public method that would be used from the generated code that serializes enum value in all other places.

Using:

Dart SDK version: 3.2.0
json_annotation: 4.8.1
json_serializable: 6.7.1
faisalansari0367 commented 8 months ago

@ekuleshov I faced the same issue. Would it be a good idea to create a .g file for the enum and generate it's enum map there to use it in the rest of the application?

ekuleshov commented 8 months ago

@ekuleshov I faced the same issue. Would it be a good idea to create a .g file for the enum and generate it's enum map there to use it in the rest of the application?

I don't think you can do that without changes in the json_serializable generator. Currently generated code uses these private const maps in the generated toJson and fromJson implementations.

In any case the model's .dart is the one that is being imported everywhere else, so making these maps globally public and have the generated code in all dependent classes use them instead of generating again should address this.

badver commented 4 months ago

I had the same problem: a quite big Enum with around 30 values. And its map was copied in every generated code.

Here's a solution, maybe it's not elegant but it works and significantly reduces generation time.

_$ErrorCodeDtoEnumMap generated in error_code_dto.g.dart so you don't need to care about it. New values go there.

Now all other models just reuse model's toJson and fromJson without duplication.

Enum:

import 'package:json_annotation/json_annotation.dart';

part 'error_code_dto.g.dart';

@JsonEnum(alwaysCreate: true)
enum ErrorCodeDto {
  @JsonValue('CredentialsInvalid')
  credentialsInvalid,

  /// many other values ...

  @JsonValue('ValidationError')
  validationError,

  @JsonValue('Unknown')
  unknown;

  factory ErrorCodeDto.fromJson(dynamic value) =>
      $enumDecodeNullable(
        _$ErrorCodeDtoEnumMap,
        value,
        unknownValue: ErrorCodeDto.unknown,
      ) ??
      ErrorCodeDto.unknown;

  String? toJson() =>
      _$ErrorCodeDtoEnumMap[this] ??
      _$ErrorCodeDtoEnumMap[ErrorCodeDto.unknown];
}

Example of generated model:

CommonResponse _$CommonResponseFromJson(Map<String, dynamic> json) =>
    CommonResponse(
      success: json['success'] as bool,
      message: json['message'] as String?,
      errorCode: json['errorCode'] == null
          ? null
          : ErrorCodeDto.fromJson(json['errorCode']),
    );
ekuleshov commented 4 months ago

I had the same problem: a quite big Enum with around 30 values. And its map was copied in every generated code.

Here's a solution, maybe it's not elegant but it works and significantly reduces generation time.

Of course. But that is quite a bit of manually written boiler plate code that could be generated. Also, this won't work when you have a 3rd party model from another module.