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

Question: Possible to serialize & deserialize a field of type Map<String, dynamic> ? #1257

Closed
jimmyff opened this issue Jul 12, 2023 · 14 comments

Comments

@jimmyff
Copy link

jimmyff commented Jul 12, 2023

Hey, I've attempted to create a class that will cache the output from an external API. I'd like the output from the api to be stored as a generic json: Map/BuilMap<String, dynamic> however the serializer complains about that. I know I could json encode the data and just store it as a string, but then i'd loose the nice perk that I can query the data field if it's stored in firestore as a Map/Object.

Is this kind of thing possible I do I need to break this in to two documents and handle them both individually?

//...
abstract class CachedFile implements Built<CachedFile, CachedFileBuilder> {
  static Serializer<CachedFile> get serializer => _$cachedFileSerializer;

  DateTime get created;
  DateTime get updated;

  BuiltMap<String, dynamic> get data;

//...

with serializer:

      ..addBuilderFactory(
          const FullType(BuiltMap, [FullType(String), FullType(dynamic)]),
          () => MapBuilder<String, dynamic>())

The above fails with Bad state: No serializer for '_Map<String, dynamic>'. when I try to serialize.

@jimmyff
Copy link
Author

jimmyff commented Jul 12, 2023

I think the issue is the data that I'm serializing, contains maps, lists etc. That's why I'm getting the _Map error. So I guess the question is there a way to make the dynamic part work, I'm guessing not

@ahmednfwela
Copy link
Contributor

from my experience it works just fine, can you share your serializer configuration ?

@jimmyff
Copy link
Author

jimmyff commented Jul 13, 2023

Oh interesting - thanks @ahmednfwela -I had given up thinking it wasn't possible.

final Serializers serializers = (_$serializers.toBuilder()
      ..addPlugin(StandardJsonPlugin())
      ..addBuilderFactory(
          const FullType(BuiltMap, [FullType(String), FullType(dynamic)]),
          () => MapBuilder<String, dynamic>())
    )
    .build();

Could you possibly share how your class & seriazliers look to get it working?

@jimmyff
Copy link
Author

jimmyff commented Jul 13, 2023

Even just trying this fails for me:

      final json = <String, dynamic>{
        'name': 'foobar',
        'age': 99,
        'timestamp': {'value': true},
        'lists': [1, 2, 42],
      };

      final serialized = serializers.serialize(
          BuiltMap<String, dynamic>.from(json),
          specifiedType:
              const FullType(BuiltMap, [FullType(String), FullType(dynamic)]));

with Bad state: No serializer for '_Map<String, bool>'.

@ahmednfwela
Copy link
Contributor

ahmednfwela commented Jul 13, 2023

@jimmyff can you try using BuiltMap<String, JsonObject?> instead of dynamic ?

@jimmyff
Copy link
Author

jimmyff commented Jul 13, 2023

@jimmyff can you try using BuiltMap<String, JsonObject?> instead of dynamic ?

Ohh interesting, thanks! I've got this working:

    test('serializing and deserializing json maps', () {
      final json = <String, dynamic>{
        'name': 'foobar',
        'age': 99,
        'timestamp': {'value': true},
        'lists': [1, 2, 42],
      };

      final serialized = serializers.serialize(
          BuiltMap<String, JsonObject>.from(
              json.map((key, value) => MapEntry(key, JsonObject(value)))),
          specifiedType: const FullType(
              BuiltMap, [FullType(String), FullType(JsonObject)]));

      final deserialized = BuiltMap<String, JsonObject>(serializers.deserialize(
          serialized,
          specifiedType: const FullType(
              BuiltMap, [FullType(String), FullType(JsonObject)])));

      final unpacked =
          deserialized.map((key, value) => MapEntry(key, value.value)).asMap();

      expect(unpacked, equals(json));
    });

It's quite verbose with having to pack and unpack the maps with JsonObject's -is there a more elegant way of doing this?

I'll try to get this working on my class now. Thanks again

@jimmyff
Copy link
Author

jimmyff commented Jul 13, 2023

hmm, maybe the whole map should just be type JsonObject!

@ahmednfwela
Copy link
Contributor

be careful you don't hit this bug if you have possible null values in your map #1242

the proper way in your example:

test('serializing and deserializing json maps', () {
  final json = <String, dynamic>{
    'name': 'foobar',
    'age': 99,
    'timestamp': {'value': true},
    'lists': [1, 2, 42],
    'nullable': null,
  };

  final serialized = serializers.serialize(
      BuiltMap<String, JsonObject?>.from(
          json.map((key, value) => MapEntry(key, value == null ? null : JsonObject(value)))),
      specifiedType: const FullType(
          BuiltMap, [FullType(String), FullType.nullable(JsonObject)]));

  final deserialized = BuiltMap<String, JsonObject?>(serializers.deserialize(
      serialized,
      specifiedType: const FullType(
          BuiltMap, [FullType(String), FullType.nullable(JsonObject)])));

  final unpacked =
      deserialized.map((key, value) => MapEntry(key, value?.value)).asMap();

  expect(unpacked, equals(json));
});

@ahmednfwela
Copy link
Contributor

hmm, maybe the whole map should just be type JsonObject!

yes I suggest changing the property in your class to be BuiltMap<String, JsonObject?> altogether, to avoid packing/unpacking

@jimmyff
Copy link
Author

jimmyff commented Jul 13, 2023

Thanks for the null issue-noted! I'm wondering if the field can just be type JsonObject rater than a map. A bit like this:

    test('serializing and deserializing JsonObject', () {
      final json = <String, dynamic>{
        'name': 'foobar',
        'age': 99,
        'timestamp': {'value': true},
        'lists': [1, 2, 42],
      };

      final serialized = serializers.serialize(JsonObject(json),
          specifiedType: const FullType(JsonObject));

      final deserialized = serializers.deserialize(serialized,
          specifiedType: const FullType(JsonObject)) as JsonObject;

      final unpacked = BuiltMap<String, dynamic>(deserialized.value).asMap();

      expect(unpacked, equals(json));
    });

Anyway, thanks for all your help @ahmednfwela

@jimmyff jimmyff closed this as completed Jul 13, 2023
@ahmednfwela
Copy link
Contributor

@jimmyff if the data field can accept any json value (strings/numbers/lists/objects/...) then it needs to be JsonObject? if it only accepts json objects, it needs to be Map<String, JsonObject?>

@jimmyff
Copy link
Author

jimmyff commented Jul 13, 2023

@ahmednfwela Ah yes, good point, I loose typing benefits by going for the less verbose code!

@ahmednfwela
Copy link
Contributor

personally I would write extension methods on Map<String,dynamic> to help pack/unpack the BuiltMap object

@jimmyff
Copy link
Author

jimmyff commented Jul 13, 2023

Ahh thats a really good idea. I'll have a play with that too thanks

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

2 participants