Skip to content

how to deserialize #131

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

Closed
zoechi opened this issue Mar 1, 2017 · 31 comments
Closed

how to deserialize #131

zoechi opened this issue Mar 1, 2017 · 31 comments

Comments

@zoechi
Copy link
Contributor

zoechi commented Mar 1, 2017

I find it hard to find information about how to deserialize

I'd expect something like

    final accJson = JSON.decode("""
{
    "profileId": 1,
    "emailAddress": "[email protected]",
    "dob": "1984-02-05",
    "premium": false,
    "membershipId": null,
    "adverts": true,
    "homeLatitude": 53.7966667,
    "homeLongitude": -1.5340407,
    "latitude": 53.7939665,
    "longitude": -1.5393381,
    "keyValues": {
        "gender": "male"
    }
}
""") as Map<String, dynamic>;

final acc = Account.deserialize(accJson)

but that doesn't work

ERROR: The method 'deserialize' isn't defined for the class 'Serializer<Account>'. ([myproj] test/models.dart:31)
@davidmorgan
Copy link
Collaborator

Serialization is done using the Serializers class, not the individual Serializer classes. See example:

https://github.com/google/built_value.dart/blob/master/example/lib/example.dart

To get a Serializers instance you use codegen:

https://github.com/google/built_value.dart/blob/master/example/lib/serializers.dart

Note the expected JSON format does not match what you have in your code snippet. If you want a map-based serializer, you need to also add the StandardJsonPlugin to Serializers, as here:

https://github.com/google/built_value.dart/blob/master/end_to_end_test/test/values_serializer_test.dart#L71

Hope that helps!

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

Ok, the hint with the StandardJsonPlugin is definitely helpful.

I tried

final a = serializers.deserialize(m, specifiedType: const FullType(Account)) as Account;

which is pretty cumbersome, there is probably a better way
and it doesn't work anyway.

The serializer seems to expect a list instead of a map

Failed to load "/home/zoechi/dart/customers/myproj/test/models.dart":
type 'int' is not a subtype of type 'List' in type cast where
  int is from dart:core
  List is from dart:core

dart:core                                                   Object._as
package:built_value/src/built_json_serializers.dart 100:32  BuiltJsonSerializers._deserialize
package:built_value/src/built_json_serializers.dart 91:18   BuiltJsonSerializers.deserialize
package:built_value/src/built_json_serializers.dart 119:16  BuiltJsonSerializers._deserialize
package:built_value/src/built_json_serializers.dart 91:18   BuiltJsonSerializers.deserialize
package:myproj/src/models/account.g.dart 93:42          _$AccountSerializer.deserialize
package:built_value/src/built_json_serializers.dart 123:27  BuiltJsonSerializers._deserialize
package:built_value/src/built_json_serializers.dart 91:18   BuiltJsonSerializers.deserialize
test/models.dart 31:27                                      main.<async>.<fn>
package:test                                                group
test/models.dart 13:3                                       main.<async>

Process finished with exit code 1

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

I just assumed that it accepts this JSON structure.
I'll have yet to check how the JSON looks like when I serialize an instance.
Should above JSON deserialize? Do I have to provide a custom plugin?
Or is there no way to make built_value work with such a JSON structure?

@davidmorgan
Copy link
Collaborator

That stack trace looks like it's coming from a field deserialization. What types it's expected for each field will depend on the field declarations.

It should be possible to deserialize something like that. But, yes, easiest way to see what's expected is to create one and serialize it, either with or without StandardJsonPlugin.

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

I just tried to serialize

    final acc = new Account((b) => b
      ..profileId = 1
      ..emailAddress = '[email protected]'
      ..dob = '1984-02-05');

final x = serializers.serialize(acc, specifiedType: const FullType(Account));
Failed to load "/home/zoechi/dart/customers/myproj/test/models.dart": Invalid argument(s): Standard JSON requires specifiedType.

package:built_value/standard_json_plugin.dart 18:7         StandardJsonPlugin.beforeSerialize
package:built_value/src/built_json_serializers.dart 36:18  BuiltJsonSerializers.serialize
package:built_value/src/built_json_serializers.dart 68:16  BuiltJsonSerializers._serialize
package:built_value/src/built_json_serializers.dart 38:18  BuiltJsonSerializers.serialize
package:myproj/src/models/account.g.dart 24:30         _$AccountSerializer.serialize
package:built_value/src/built_json_serializers.dart 72:14  BuiltJsonSerializers._serialize
package:built_value/src/built_json_serializers.dart 38:18  BuiltJsonSerializers.serialize
test/models.dart 38:21                                     main.<async>.<fn>
package:test                                               group
test/models.dart 13:3                                      main.<async>

Process finished with exit code 1

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

That stack trace looks like it's coming from a field deserialization.

Looks like you're right. Previously I debugged in a bit and it failed very early.
Seems this is now a different exception. I'll have a closer look.

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

// account.g.dart

Object serialize(Object object,
      {FullType specifiedType: FullType.unspecified}) {
    var transformedObject = object;
    for (final plugin in _plugins) {
      transformedObject =
          plugin.beforeSerialize(transformedObject, specifiedType);
    }
    var result = _serialize(transformedObject, specifiedType); // <<<=====

is called with object = 1, transformedObject = 1, specifiedType = {FullType} num
it looks like it should just return the value, instead of trying to serialize it.

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

Without the StandardJsonPlugin plugin, serialization works though

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

Looks like num is the problem

_typeToSerializer.containsKey(num) = false
_typeToSerializer.containsKey(int) = true

@davidmorgan
Copy link
Collaborator

Yes, was just about to write that :) ... there's no serializer for num. Default serialization will pick the int or double serializer for you, but the StandardJsonPlugin requires an exact match on type type.

This is an oversight -- filed an issue

#132

Thanks!

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

I changed num to int and double

Now this code fails because specifiedType.parameters.isEmpty == true

  bool _alreadyHasStringKeys(FullType specifiedType) =>
      specifiedType.root != BuiltMap ||
      specifiedType.parameters[0].root == String;

@davidmorgan
Copy link
Collaborator

Could you please post the fields from your class?

I guess you have a BuiltMap with no type parameters, it needs to be e.g. BuiltMap<String, String>.

Note that dynamic types aren't supposed with StandardJsonPlugin, only with default serialization. i.e. the default serialization can serialize BuiltMap<Object, Object>, but StandardJsonPlugin can't. There's an issue open for that #102

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

abstract class Account implements Built<Account, AccountBuilder> {
  factory Account([void updates(AccountBuilder b)]) = _$Account;

  Account._();

  static Serializer<Account> get serializer => _$accountSerializer;

  @nullable
  int get profileId ;

  @nullable
  String get emailAddress ;

  @nullable
  String get dob ;

  @nullable
  bool get premium ;

  @nullable
  int get membershipId ;

  @nullable
  bool get adverts ;

  @nullable
  double get homeLatitude ;

  @nullable
  double get homeLongitude ;

  @nullable
  double get latitude ;

  @nullable
  double get longitude ;

  @nullable
  BuiltMap get keyValues ;
}

@davidmorgan
Copy link
Collaborator

Thanks. It's that BuiltMap that's the problem.

Built collections anyway always require type parameters -- so you can't create a "BuiltMap" or a "BuiltMap<dynamic, dynamic>'.

I'll open an issue to handle this error case better.

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

Also not BuiltMap<String, dynamic>?

@davidmorgan
Copy link
Collaborator

Right, has to be BuiltMap<String, Object>.

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

I guess it's enough when one of the default JSON serializable types is supported for Object
(int, num, double, String, bool, List, Map)

I don't see a way to support custom types here anyway, except with a custom reviver like supported in JSON.decode(...).
Don't know if built_value supports anything like that. I think it would make sense.

@davidmorgan
Copy link
Collaborator

The default built_value serialization supports any type for Object -- this is the main feature of built_value serialization, in fact :)

Unfortunately I don't think Object will work as you want with StandardJsonPlugin. #102 would have to be implemented first -- basically, we add an extra field with the type name. There isn't any fallback to checking the default JSON serialization types; this would be problematic because e.g. there's no distinction between int and double.

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

    final x =  serializers.serialize(acc, specifiedType: const FullType(Account));
    final a =  serializers.deserialize(m, specifiedType: const FullType(Account)) as Account;

is quite cumbersome. Any suggestion how to make this easier to use?

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

Just saw, BuiltMap<String, SimpleValue> should also work.
"add an extra field" won't work well with JSON where others specify the format.
I guess using int and num depending on whether the value has decimals would do - at least for me.

@davidmorgan
Copy link
Collaborator

With the default serialization you don't need "specifiedType". With StandardJsonPlugin you do, and it's currently a bit ugly, I agree. You could wrap the serialization:

Object serialize(Object object) {
return serializers.serialize(object, specifiedType: new FullType(object.runtimeType));
}

but the deserialize is a bit harder, you could wrap it to look like "deserialize(m, Account) as Account".

Re: the extra field, yes, that's a good point. I hadn't thought about supporting "jsony maps". One way would be to add a JsonPrimitive class that can deserialize from any JSON primitive. What do you think?

(This would start to look a lot a Java Json library.)

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

final x =  serializers.serialize(acc);
Failed to load "/home/zoechi/dart/customers/rocketware/datingnode.dart/test/models.dart": Invalid argument(s): Standard JSON requires specifiedType.
final x = serializers.serialize(acc, specifiedType: const FullType(Account));

works fine though.

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

a JsonPrimitive class that can deserialize from any JSON primitive

Not sure in detail how you imagine that, but sounds good ;-)

@davidmorgan
Copy link
Collaborator

Right now with StandardJsonPlugin you'll need to wrap the "serialize" method with something that creates the FullType for you. (With the default serialization, you don't to do anything, it just works)

The reason there's mismatch here is because built_json was originally designed to be the serialization on both sides of the pipe -- i.e. you use it on the server in the VM and on the client with dart2js. Only later did it transpire that people would very much like to have interop with other json things :) ... and so StandardJsonPlugin was added last month. It's a bit rough around the edges -- thanks for pointing out a few major gaps :)

I'll create an issue for supporting Map<String, dynamic> somehow.

@davidmorgan
Copy link
Collaborator

#134

@zoechi
Copy link
Contributor Author

zoechi commented Mar 1, 2017

I think my questions to this topic are answered. Thanks a lot!

@zoechi zoechi closed this as completed Mar 1, 2017
@davidmorgan
Copy link
Collaborator

FYI #146 adds JsonObject and "serializeWith/deserializeWith" methods for easier serialization/deserialization when you know the type. See the example here:

https://github.com/google/built_value.dart/pull/146/files#diff-62ae4492c44d3d5e9c5eb850acdbdd56

Basically you do:

final account = serializers.deserializeWith(Account.serializer, serializedData);

@techyrajeev
Copy link

@davidmorgan do I need to import serializers in each models?
Since I am getting this error 'serializers' is undefined.

String toJson() {
    return json.encode(serializers.serializeWith(HomeTeam.serializer, this));
  }


@davidmorgan
Copy link
Collaborator

@techyrajeev Yes, you'll need to import it anywhere it's used.

@techyrajeev
Copy link

techyrajeev commented Aug 3, 2018

Oh I thought It will be auto injected when I run
flutter packages pub run build_runner watch

This scenario can also be auto generated since we just need to add the import to any model declared in serializers

@davidmorgan
Copy link
Collaborator

part files aren't allowed to contain imports, so the generated code can't help you there.

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