Closed gabrielmcreynolds closed 3 months ago
I have for sure never seen discriminatorKey
being used like this, but at the same time I don't see anything wrong with it from just now.
Can you add a snippet on how you call the fromJson method and what you expect + actually get as a result.
Thanks for responding :) I'm calling the fromJson
from another class. Here is a simplified version of that class:
@MappableClass(discriminatorValue: ServerTour.checkType)
class ServerTour extends Tour with ServerTourMappable {
final IList<ServerArtifact> artifacts;
const ServerTour({
required super.id,
required this.artifacts,
});
static bool checkType(value) {
return value is Map && value['businessId'] != null;
}
factory ServerTour.fromJson(Map<String, dynamic> json) =>
ServerTourMapper.fromJson(json);
}
Then I call ServerTour.fromJson(...json)
.
What is printed is:
ServerTour(id: 6f263192-ac3c-4da1-a88e-1a3eec232d4e, artifacts: [
Type: Location. ServerArtifact(location: PointLocation(.... other data)),
Type: Location. ServerArtifact(location: PointLocation(.... other data)),
)
Currently my workaround I've found is to create a changeType method in Artifact
where I manually reconstruct the Artifact
using a MappingHook in my Tour
class like the following:
// in my artifact.dart class
@mustBeOverridden
Artifact<G> changeType<G extends Location>() {
return Artifact(
title: title,
description: description,
location: location as G,
);
}
Then I add the following MappingHook to my IListTour
where the JSON serialization occurs:
class ArtifactsHook extends MappingHook {
const ArtifactsHook();
@override
Object? afterDecode(Object? value) {
if (value is IList<ServerArtifact>) {
return value.map((artifact) {
if (artifact.location is PointLocation) {
return artifact.changeType<PointLocation>();
} else {
return artifact.changeType<PolygonLocation>();
}
}).toIList();
}
return null;
}
}
While this works for now, I hope this is not the long-term solution as everytime I change a property in Artifact
I would now have to change each changeType
property as well.
Ok I got your problem now. However this issue is not really a bug and works as supposed to. The reason is because deserialization works in a top-down manner for choosing the type in nested class structures. So the generic type for Artifact is chosen before Location is decoded, therefore it does not know the exact type of Location yet. I generally think this behavior is correct as the top class may also want to have other values for the generic type.
So for your case I see two solutions. Either use a hook like you did to modify the class after decoding, or reverse the decoding order for location to force a correct decoding of the generic type.
Here is a hook for the 2nd option to apply to Artifact:
class ArtifactHook extends MappingHook {
const ArtifactHook();
@override
Object? beforeDecode(Object? value) {
if (value is Map<String, dynamic>) {
if (value['location'] case Map<String, dynamic> l) {
var location = LocationMapper.fromMap(l);
return ArtifactMapper.ensureInitialized().decoder(
{...value, 'location': location},
DecodingContext(args: () => [location.runtimeType]),
);
}
}
return value;
}
}
This swaps the normal order around by first decoding the Location, and then using its type as generic type argument for decoding the Artifact. Also here I pass an updated map to ArtifactMapper to skip decoding the Location twice.
Thanks for the second hook. That is a much better option. Had to change it a little bit because it was a list but overall pleasantly surprised by the flexibility that dart_mappable provides compared with alternatives. Thanks for all your work on this package!
I cannot figure out how to get the correct type of a class when it is deserialized from JSON.
I have a class
Location
that has two subclasses as seen below:Now I have another class that utilizes
Location
as a generic parameter as seen below:Now, whenever I am using serializing an
Artifact
from JSON it correctly sets thelocation
property to be the correct location (i.e.PointLocation
orPolygonLocation
) however the generic type ofArtifact
is alwaysLocation
notPointLocation
even if location field is aPointLocation
class as shown in thetoString
ofServerArtifact
the type ofT
isLocation
.I can't just do
MapperContainer.globals.fromJson<Artifact<PointLocation>>(...);
because typicallyArtifact
is being deserialized from a list where eachArtifact
could have aPointLocation
or aPolygonLocation
type parameter.I see in the docs that it is recommended to have a type field in the JSON, however I cannot control the JSON response from the server so cannot add that type field, and I thought that by adding a discriminator the generator would be able to figure it out.
Not sure if this is a bug or (more likely :) I'm doing something wrong so any help is appreciated!