Closed larssn closed 1 month ago
I understand what you're asking for, but I'm confused about what you want it for.
It looks like you're comparing the serialized form of initial
to a deserialized value
? Or did you mean serializers.deserialize
on the line before changes.remove
?
What is toMap
doing in this code?
Or did you mean serializers.deserialize on the line before changes.remove?
Yes, I meant deserialize.
toMap
resides on a BuiltValue class, in this case Customer:
Map<String, dynamic> toMap() => serializers.serializeWith(serializer, this) as Map<String, dynamic>;
Our setup is a mix of built_value, Firestore and Flutter, and since Firestore supports incremental updates (as json Maps), it makes sense to be able to compare your built_value field with a change. If the change is identical with the built_value field, then the change is removed from the json sent to Firestore.
In the above example, we have a complex type: BuiltMap<String, Ticket> tickets
.
In settings for customer, a change for tickets can occur:
// newTickets comes out of a UI picker.
final newMap = newTickets == null
? null
: BuiltMap.of(
Map.fromEntries(newTickets.map((e) => MapEntry(e.id!, (tickets ?? <String, Ticket>{})[e.id] ?? Ticket.empty(e.id!)))));
setState(() {
// newMap is a BuiltMap<String, Ticket> here, and we add it as a change.
widget.change.add('tickets', newMap);
});
Since I don't have reflection via Mirrors in Flutter, there's no way I can do a customer['tickets'] == newMap
(attempting to compare the BuiltMap<String, Ticket> newMap
with the BuiltMap<String, Ticket> tickets
field on the customer).
So to work around this limitation, and I first serialize the customer object to a map, and dynamically look up the tickets map:
serializers.serializeWith(Customer.serializer, customerInstance)['tickets'].
In order to do a deep comparison with this new Map<String, Map> tickets
against the BuiltMap<String, Ticket> newMap
I need to dynamically deserialize the field, back to a BuiltMap.
That is what I'm attempting to do, and in order to do so, I need to dynamically access the FullType.
Sorry for the long winded explanation. I hope it makes sense.
And thanks for a stellar plugin! Blows Freezed out of the water.
Thanks for the extra detail :) I think I understood now.
Adding some kind of class/field metadata to the generated output is something I've thought about for a while, but it's never quite seemed important enough to push through.
Adding the FullType
for each field sort of makes sense but it's a lot of additional generated code for a fairly narrow use case.
The 'serializers' internal map of builder generators is almost what you need, and serializers
would be a reasonable place for a Map<Type, FullType>
, but populating that would be both a change to generated code and require updates to currently existing manually written addBuilder
, which is awkward.
I think there is one way that might work already, you can write a serializer plugin
class FullTypesSerializerPlugin {
final Map<Type, FullType> types = {};
Object? beforeSerialize(Object? object, FullType specifiedType) {
if (object != null) types[object.runtimeType] ??= specifiedType;
return object;
}
Object? afterSerialize(Object? object, FullType specifiedType) => object;
Object? beforeDeserialize(Object? object, FullType specifiedType) => object;
Object? afterDeserialize(Object? object, FullType specifiedType) {
if (object != null) types[object.runtimeType] ??= specifiedType;
return object;
}
}
with this created as a global variable somewhere and installed
plugin = FullTypesSerializerPlugin();
serializers = (serializers.toBuilder()..addPlugin(plugin)).build();
any type you deserialize/serialize will end up with all its runtimeType->FullType mappings stored in plugin.types
. Does that work? :)
Nice!! That totally works!
Makes the API much simpler to use. Thanks!
Feel free to close the issue, if needed.
Great, glad I could help. Thanks for the positive feedback :)
So inside the autogenerated serializer class for a given entity, within the
serialize
method, theFullTypes
are inaccessible.For example:
I wish I had a way, for a given serializer, to access the
specifiedType
above.So in the above case, something like:
Customer.serializer.fullTypes['tickets']
which would return:const FullType(BuiltMap, const [const FullType(String), const FullType(Ticket)])
.This would make dynamically serializing specific properties on BuiltValue classes much easier, without having to serialize the entire class.
The specific use case is a comparison method in our case, for detecting if there's a change to persist, or if the value is equal to the initial value: