Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting multiple keys for the same field during deserialization #1169

Open
sabinbajracharya opened this issue Jun 22, 2022 · 3 comments
Open
Assignees

Comments

@sabinbajracharya
Copy link

sabinbajracharya commented Jun 22, 2022

Problem statement:

I have a model called "Calendar" which needs to parse either the following json

{
    "cid": "101",
    "ut": "20230715",
    "cb": "admin@admin.com",
    "st": 1
  }

or the following one depending on some use cases

{
    "calendar_id": "101",
    "updated_time": "20230715",
    "created_by": "admin@admin.com",
    "status": 1
  }

Basically the value will be the same only difference is the key. The reason for this is that the "Calendar" model has lots of fields and this data is in a file which is kept in the asset folder of the application. So, the fields are shortened to reduce some space taken by the file.

For deserialization, I need to be able to parse the json with both possible keys for the same value. But for serialization I only need the keys shown in the second json so no problem with serialization.

The interim solution that I am using is the following:

  • Extend the "_$CalendarModelSerializer" generated by built_value and override the "deserialize" method to add two cases for each value.
  • Use this extended serializer to decode the json but use the originally generated serializer to serialize the model.
class _$CalendarModelWithMultipleKeySerializer extends _$CalendarModelSerializer {
  @override
  CalendarModel deserialize(
      Serializers serializers, Iterable<Object?> serialized,
      {FullType specifiedType = FullType.unspecified}) {
    final result = CalendarModelBuilder();

    final iterator = serialized.iterator;
    while (iterator.moveNext()) {
      final key = iterator.current! as String;
      iterator.moveNext();
      final Object? value = iterator.current;
      switch (key) {
        case 'calendar_id':
        case 'cid':
          result.calendarId = serializers.deserialize(value,
              specifiedType: const FullType(String))! as String;
          break;
        case 'status'
        case 'st':
          result.status = serializers.deserialize(value,
              specifiedType: const FullType(int))! as int;
          break;
        case 'created_by':
        case 'cb':
          result.createdBy = serializers.deserialize(value,
              specifiedType: const FullType(String))! as String;
          break;
        case 'updated_time':
        case 'ut':
          result.updatedAt = serializers.deserialize(value,
              specifiedType: const FullType(DateTime))! as DateTime;
          break;
      }
    }
    return result.build();
  }
}

So, is there any possible way to add two "wireName" for the same field? Or any workaround so that I don't have to manually add two cases everytime I need to change or add a new field.

@davidmorgan
Copy link
Collaborator

I think that this PR

#1119

exactly does what you want by adding an oldWireNames setting that can list multiple alternative wire names for deserializing a field. It does not let you use alternatives for serializing.

--but there hasn't been any update there in a while; let me ask. Thanks.

@davidmorgan davidmorgan self-assigned this Jun 23, 2022
@sabinbajracharya
Copy link
Author

@davidmorgan That's exactly what I am looking for and it looks like the PR is almost done. Thanks!

@quyenvsp
Copy link

you can simple write plugin for it, something like this

import 'package:built_value/serializer.dart';
import 'package:collection/collection.dart';
import 'package:common/utils/logger.dart';

class RewritePlugin implements SerializerPlugin {
  /// Sync targetKey and replaceKey value when Serialize / Deserialize
  /// ```
  /// const _rewriteMap = {
  ///   Null: <String, String>{
  ///     'targetKey': 'replaceKey1,replaceKey2,replaceKey3',
  ///   },
  /// };
  ///
  /// final _apiSerializers = (serializers.toBuilder()
  ///     ..addPlugin(RewritePlugin(map: _rewriteMap))
  ///     ..addPlugin(StandardJsonPlugin()))
  ///   .build();
  /// ```
  RewritePlugin({Map<Type, Map<String, String>?>? map})
      : _map = Map.unmodifiable(map ?? {});

  final Map<Type, Map<String, String>?> _map;

  @override
  Object? beforeSerialize(Object? object, FullType specifiedType) {
    return object;
  }

  @override
  Object? afterSerialize(Object? object, FullType specifiedType) {
    return _syncValue(object, specifiedType);
  }

  @override
  Object? beforeDeserialize(Object? object, FullType specifiedType) {
    return _syncValue(object, specifiedType);
  }

  @override
  Object? afterDeserialize(Object? object, FullType specifiedType) {
    return object;
  }

  Object? _syncValue(Object? object, FullType specifiedType) {
    try {
      if (object is Map<String, dynamic>) {
        final result = Map.from(object);
        final global = _map[Null] ?? {};
        final map = {...global, ..._map[specifiedType.root] ?? {}};
        map.forEach((target, value) {
          final replaces = value.split(',').map((e) => e.trim()).toList();
          final replaceHasValue =
              replaces.firstWhereOrNull((element) => result[element] != null);
          // Two-way sync for both Serialize / Deserialize
          // Make targetKey has value first for sure all have same value
          // Prevent replaceKey1 null replaceKey2 not null
          if (result[target] == null && replaceHasValue != null) {
            result[target] = result[replaceHasValue];
          }
          // Only continue sync all other replaces with value is null if targetKey value not null
          if (result[target] != null) {
            for (final replace in replaces) {
              result[replace] ??= result[target];
            }
          }
        });
        return result;
      }
    } catch (e, stackTrace) {
      logger.e(e.toString(), stackTrace: stackTrace);
    }
    return object;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants