gql-dart / ferry

Stream-based strongly typed GraphQL client for Dart
https://ferrygraphql.com/
MIT License
602 stars 116 forks source link

OfflineMutationTypedLink - Bad state: No serializer for '_Map<String, dynamic>'. #493

Closed francescreig closed 1 year ago

francescreig commented 1 year ago

Hello,

I have been integrating the OfflineMutationTypedLink with a mutationQueue that @akinsho started time ago on PR #147 but I have encountered into a serialization problem that arises when the request is sent to be serialized before added to que mutationQueue box (on Hive). The error that prompts is: Bad state: No serializer for '_Map<String, dynamic>' and it happens when executing mutationQueueBox.add(serializers.serialize(request)); line and I think that it should be because some Serializers issue and not regarding any implementation on the offline link itself.

Do I need to create a custom serializer for this? Does not JsonSerializer already implemented take care of this?

Thank you before hand.

knaeckeKami commented 1 year ago

mutationQueueBox.add(serializers.serialize(request));

There is a lot going on this line, and with no stacktrace I can only guess what the actual problem is.

Does hive throw the error or the serializer? Do you have custom scalars in your schema that are JSON maps?

francescreig commented 1 year ago

Hello again, thank you for your quick response. The error is thrown by the the serializer (I guess) and yes I have custom scalars that are JSON maps that I serialize with the JsonObject. A part from that, the requests are being sent correctly when I do not use the OfflineMutationTypedLink, but I am not sure what happens on this line. Does JsonObject not serialize Map<String, dynamic>?

By the way, this is my build.yaml file in order to enable the custom scalar JsonObject:

targets:
  $default:
    builders:
      ferry_generator|graphql_builder:
        enabled: true
        options:
          schema: path/to/schema.graphql
          type_overrides:
            ObservationDataScalar:
              name: JsonObject
              import: "package:built_value/json_object.dart"

      ferry_generator|serializer_builder:
        enabled: true
        options:
          schema: path/to/schema.graphql
          custom_serializers:
            - import: "package:built_value/src/json_object_serializer.dart"
              name: JsonObjectSerializer
knaeckeKami commented 1 year ago

try adding the

type_overrides: also in the serializer_builder

francescreig commented 1 year ago

So I have tried to add type_overrides on the serializer_builder like this:

targets:
  $default:
    builders:
      ferry_generator|graphql_builder:
        enabled: true
        options:
          schema: path/to/schema.graphql
          type_overrides:
            ObservationDataScalar:
              name: JsonObject
              import: "package:built_value/json_object.dart"

      ferry_generator|serializer_builder:
        enabled: true
        options:
          schema: path/to/schema.graphql
          custom_serializers:
            - import: "package:built_value/src/json_object_serializer.dart"
              name: JsonObjectSerializer
          type_overrides:
            ObservationDataScalar:
              name: JsonObject
              import: "package:built_value/json_object.dart"

but I still get the same error. Am I defining wrong the type_overrides on the serializer_builder?

I am assigning the Map<String, dynamic> to create an JsonObject (as I have defined on the type_overrides) with the factory, so that JsonObject(variable). Digging into the JsonObject implementation, its type is not Map<String, Object?> so that it will try to cast it anyway. On the other hand, I expect than other people will also use a JSON Map as a custom scalar, so probably this is the root problem?

knaeckeKami commented 1 year ago

I have a similar setup but I use a custom wrapper type + serializer for the json map, not the built_value JsonObject. I was not even aware that this exists, is it new?

I don't see anything wrong with your setup, it might be either a bug in built_value (I saw some issues related JsonObject serialization) or in how ferry handles this JsonObject type.

Can you maybe provide a minimal reproducible example that build a request where the serializers.serialize(request) fails?

francescreig commented 1 year ago

Okay, so thank you for your quick response again.

Finally I make it working by creating a custom serializer but with the JsonSerializer that built_value offers and NOT using the JsonObject that also it's on the build_value package. JsonObject does not override the serialize and deserialize methods so that's why when executing serializers.serialize(requests) failed. If someone has faced the same issue, just remember to map its type T as Map<String, dynamic>, so for exemple a GenericScalarSerializer would be:

import 'package:built_value/serializer.dart';
import "package:gql_code_builder/src/serializers/json_serializer.dart";

class GenericScalarSerializer extends JsonSerializer<Map<String, dynamic>> {
  @override
  Iterable<Type> get types => [Map<String, dynamic>];

  @override
  String get wireName => 'GenericScalar';
}

and we don't need to override the serialize/deserialize methods since JsonSerializer class it already handles the JSON serialization part. Also a key part was to override properly the wireName on the custom serializer.

On the other hand, do not forget to add the type Map<String, dynamic> to your custom type on type_overrides at build.yaml file.

Finally, I think it would be useful to add a little bit of explanation on the docs part on how to deal with JSON scalars at serialization/deserialization part, like a continuation on whats already written on Creating a custom Serializer. I think it will help a lot to other people 🙂