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.55k stars 397 forks source link

How to serialize built-in enums to use `index` instead of `name`? #1286

Closed bambinoua closed 1 year ago

bambinoua commented 1 year ago

I have a class with internal enum ThemeMode. For example:

class Screen {
  Screen(this.mode);

  final ThemeMode mode;
}

How can I force to serialize mode as mode.index instead of default mode.name? It is naturally I cannot annotate ThemeMode with JsonEnum.

kevmoo commented 1 year ago

https://github.com/google/json_serializable.dart/commit/caf3a15e708845687915751172873f5709e2d3d9#diff-ffefe2a31a6ea26bece47cfb920fbab950b450115869414eebc15a9ff49192fdR41

bambinoua commented 1 year ago

@kevmoo again. ThemeMode is enum from flutter/lib/src/material/app.dart. I cannot modify flutter source file. I cannot annotate enum ThemeMode with @JsonEnum annotation.

So the question is how to force serialize framework's enums as index not name? Is it possible? If not is it possible to create a parameter in @JsonSerializable to control this?

P.S. I do not want to create a separate copy MyThemeMode instead of ThemeMode just to make what I need if this enum is already present. Moreover flutter contains enough other enums which can be used in apps.

kevmoo commented 1 year ago

Sadly you cannot serialize types from pkg:flutter – see https://github.com/google/json_serializable.dart/issues/236

bambinoua commented 1 year ago

@kevmoo I do not understand. I can serialize enums from flutter moreover your package do this but the generated map contains enum names (not exact code it is just to show):

final _generatedMap = {
  ThemeMode.system, 'system',
  ThemeMode.dark, 'dark',
  ThemeMode.light, 'light',
}

I suppose it is generated using this code:

final _generatedMap = Map.fromEntries(ThemeMode.values.map((mode) => MapEntry(mode, mode.name)));

And then map's string values are used for serialization/deserialization, i.e.

{
  "mode": "dark",
}

What I need is:

final _generatedMap = {
  ThemeMode.system, 0,
  ThemeMode.dark, 1,
  ThemeMode.light, 2,
}

which allows this:

{
  "mode": 0,
}

If your code can generate map with names (of course it have access to any flutter structures and _buildrunner can read any structure) then it can generate map with ids.

I think it is enough to add some parameter to @JsonSerializable which will define how to generate enum map. And then user can decide what he needs:

Map<String,dynamic> _generatedMap;
if(useEnumIndex) {
  _generatedMap = Map.fromEntries(ThemeMode.values.map((mode) => MapEntry(mode, mode.index)));
} else {
  _generatedMap = Map.fromEntries(ThemeMode.values.map((mode) => MapEntry(mode, mode.name)));
}
bambinoua commented 1 year ago

@kevmoo so what do you think?

kevmoo commented 1 year ago

This is a feature request, I guess. To allow customizing the encoding of enums you don't own.

It's pretty tricky, honestly.

bambinoua commented 1 year ago

@kevmoo

It's pretty tricky, honestly.

Why? I don't understand.

Here it is a simple class with framework's enum:

part 'app_state.g.dart';

@JsonSerializable()
class AppState {
  AppState(this.mode);

  final ThemeMode mode;
}

Here it is a generated code:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'app_state.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

AppState _$AppStateFromJson(Map<String, dynamic> json) => AppState(
      $enumDecode(_$ThemeModeEnumMap, json['mode']),
    );

Map<String, dynamic> _$AppStateToJson(AppState instance) => <String, dynamic>{
      'mode': _$ThemeModeEnumMap[instance.mode]!,
    };

const _$ThemeModeEnumMap = {
  ThemeMode.system: 'system',
  ThemeMode.light: 'light',
  ThemeMode.dark: 'dark',
};

So you somehow know how to get the name property of enum (string like system is taken from name property, right?) So all (as for me) what is required is take index property of enum. Or is it difficult?

kevmoo commented 1 year ago

@bambinoua – it's adding all of the required configuration and testing to add the feature. We need to teach add an enum-specific annotation to the field (mode) to "tunnel" down to enum. This package is already VERY complex, so I'm leery of adding more complexity.