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

Support serializing null on a per field basis - nullable vs required #912

Open
kuhnroyal opened this issue Sep 21, 2020 · 12 comments
Open
Assignees

Comments

@kuhnroyal
Copy link
Contributor

There is an OpenApi generator which generates built_value/dio based classes for OpenApi specifications.

OAS 3.0 and also JSON schema spec support the combination of fields being required but nullable - meaning they have are required in the json output but may be null. This combination can currently not be achieved with built_value on a field level.

I know that there is a (now removed on master) option @BuiltValueSerializer(serializeNulls: true) but this only works for all fields of the same type.

Is it possible to add a similar option on a per field basis?

@nullable
@BuiltValueField(serializeNulls: true)
String foo;

or

@BuiltValueField(serializeNulls: true)
String? foo;

See OpenAPITools/openapi-generator#5411

@davidmorgan
Copy link
Collaborator

This gets harder to support with null safety support :) since it forces treating all the JSON as nullable.

Once the null safe version is published I can look again at whether there is a good way to resume supporting nulls.

@kuhnroyal
Copy link
Contributor Author

I saw that null safety is now in a pre version. Why do you think this is harder to support there?
If @BuiltValueField(serializeNull: true/false) is only allowed on nullable fields String?, it should be possible to do.

This would allow the concept of required but nullable fields in JSON content which is supported by OAS-3.0.

@davidmorgan
Copy link
Collaborator

It's hard because it requires all the serializers to accept nulls; currently they don't, because we never write null so we know we never have to deserialize it.

@kuhnroyal
Copy link
Contributor Author

I see. Looked into doing this with a serializer plugin but the information would not be available at that point. Maybe additional information like this could somehow be provided in a plugin similar to FullType.

@kuhnroyal
Copy link
Contributor Author

I actually managed to do this with the JsonPlugin by generating custom serializers. With the nullsafe version this is no longer possible since I can not add null to Iterable<Object>.

I am stuck now :/

@kuhnroyal
Copy link
Contributor Author

With all the other problems around null-safety like #984 #964 this is somewhat becoming a fundamental question around the wire format that built_value is using/supporting.

Json allows for null values, so does the OpenAPI spec and so does Dart now explicitly with null-safety but the native format does not allow it. I assume most people actually use the StandardJsonPlugin and not the native wire format.
I understand that this is not necessarily the way the library was intended to be used in the first place, but this is what it has grown into.

Maybe it is time to decouple built_values immutable classes/builders from the serialization and find a way to integrate and generate different serializer implementations.

@davidmorgan
Copy link
Collaborator

Thanks--will think about it when fixing #984

@davidmorgan
Copy link
Collaborator

Thanks. I think the right direction is to allow more nulls :)

In #1001 I open up the interfaces so nulls are allowed in most places, including as top level inputs and outputs, in the serializer plugins and in structured serializer lists.

The one exception is that primitive serializers are not allowed to accept or return null. Instead of passing null to a primitive serializer, it is automatically serialized as null.

I think this will give us enough flexibility, as it should be possible to handle nulls above the level of the primitive serializer. Hopefully we don't have to force them all to check for null just to get null back :)

@jscti
Copy link

jscti commented Jan 24, 2022

Hi there, is there a workaround (custom serializer ?) to actually send null for any types (even primitives?) using builtValue/StandardJsonPlugin ?

My case is that I have a PATCH REST webservice with multiple optional properties that I can use to update only what I need to update on an entity

  • if i send {"property1": "value", "property2": "value"}, it should update the field property1 and property2 with the given value
  • if i send {"property1": null}, it should update only the field property1 with the given value (which is null) = reset/clear data
  • if i send {}, it should update nothing

Sending an empty string is not an option, unfortunately.

Thanks

@davidmorgan
Copy link
Collaborator

You can tag the serializers declaration

@BuiltValueField(serializeNulls: true)
final Serializers serializers = _$serializers;

but this is a whole program option, you can't set it per field or per class.

Is that enough for your use case?

@jscti
Copy link

jscti commented Jan 25, 2022

Thanks for your reply.

I guess you meant BuiltValueSerializer.

Seems to work perfectly, but I only got it to work when I added it in my specific class, so not globally (which is totally OK for me) :

abstract class EntityUpdateOutput implements Built<EntityUpdateOutput, EntityUpdateOutputBuilder> {
  String? get name;
  String? get color;

  EntityUpdateOutput._();
  factory EntityUpdateOutput([Function(EntityUpdateOutputBuilder b) updates]) = _$EntityUpdateOutput;

  @BuiltValueSerializer(serializeNulls: true)
  static Serializer<EntityUpdateOutput> get serializer => _$entityUpdateOutputSerializer;
}

Tried in the file where I link it all together but without success :

part 'serializers.g.dart';

@SerializersFor([
  // ...,
  EntityUpdateOutput
])
final Serializers serializers = (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

BuiltList<T> deserializeListWith<T>(Serializer<T> serializer, dynamic value) =>
    BuiltList.from(value.map((value) => serializers.deserializeWith(serializer, value)));

Am I missing something ?

Thank ;)

@dave26199
Copy link

Sorry, my mistake--I never actually used it so I forgot how it applies. On the class serializer it is, then :) glad it helped :)

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

No branches or pull requests

4 participants