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
Custom Discriminator Function #368
Comments
This is supposed to just work out of the box--a Are you getting the JSON from something other than built_value, maybe? Could you post the error you get when deserializing? Thanks! |
@davidmorgan yes I'm unfortunately trying to deal with an external API that has a data model that is very confusing.
here's my unit test that's running the code: test('dashboard model test', () {
Map<String, dynamic> json = JSON.decode(TEST_DASHBOARD);
Dashboard dashboard = _serializers.deserializeWith<Dashboard>(Dashboard.serializer, json);
expect(dashboard.entity, 8363);
print(dashboard.tiles.length);
}); edit: final Serializers _serializers = (serializers.toBuilder()..addPlugin(new StandardJsonPlugin())).build(); |
I see. Would you mind posting the JSON snippet too please? I'm sure we can
do something here :)
… |
const String TEST_DASHBOARD = """
{
"entity": 8363,
"tiles": [
{
"view": {
"containerId": 3193,
"urlExtraInfo": "detail?tab=tasks",
"lastModifiedBy": {
"name": "James",
"id": 8363
},
"aggregates": {},
"dateModified": "2015-10-20T19:42:09Z",
"viewId": 431587,
"groupByColumn": null,
"count": 10,
"description": "Test #18598",
"shared": false,
"key": "agwrsz",
"properties": {
"originContainerId": "3193",
"__widgetType": "SAVED_VIEW",
"collapsedContent": "true",
"tileColor": "bright-green"
},
"state": {
"vgrid": {
"filters": {},
"sortCol": "modified",
"sortDir": false,
"searchString": "test",
"columns": [
{
"id": "deleteTask",
"width": 30
},
{
"id": "status",
"hidden": false,
"width": 85
},
{
"id": "taskId",
"width": 75
},
{
"id": "name",
"width": 180
},
{
"id": "taskType",
"hidden": false,
"width": 180
},
{
"id": "progress",
"hidden": false,
"width": 105
},
{
"id": "owner",
"width": 180
},
{
"id": "dateDue",
"width": 150
},
{
"id": "modified",
"width": 150
},
{
"id": "lastModifiedBy",
"width": 180
},
{
"id": "creator",
"width": 180
},
{
"id": "container",
"hidden": false,
"width": 180
}
]
},
"taskTypeId": null
},
"type": "TASK",
"public": true
},
"visible": true,
"expanded": false,
"dashboard": 1121,
"order": 1,
"id": 4553,
"type": "SAVED_VIEW"
},
{
"view": {
"containerId": 3193,
"urlExtraInfo": "detail?tab=tasks",
"lastModifiedBy": {
"name": "James",
"id": 8363
},
"aggregates": {},
"dateModified": "2015-10-20T19:42:57Z",
"viewId": 431590,
"groupByColumn": null,
"count": 1,
"description": "Test #18598 (testy)",
"shared": false,
"key": "qjhpn5",
"properties": {
"originDashboardId": "1322",
"__widgetType": "SAVED_VIEW",
"originContainerId": "3193",
"tileColor": "red"
},
"state": {
"vgrid": {
"filters": {},
"sortCol": "modified",
"sortDir": false,
"searchString": "testy",
"columns": [
{
"id": "deleteTask",
"width": 30
},
{
"id": "status",
"hidden": false,
"width": 85
},
{
"id": "taskId",
"width": 75
},
{
"id": "name",
"width": 180
},
{
"id": "taskType",
"hidden": false,
"width": 180
},
{
"id": "progress",
"hidden": false,
"width": 105
},
{
"id": "owner",
"width": 180
},
{
"id": "dateDue",
"width": 150
},
{
"id": "modified",
"width": 150
},
{
"id": "lastModifiedBy",
"width": 180
},
{
"id": "creator",
"width": 180
},
{
"id": "container",
"hidden": false,
"width": 180
}
]
},
"taskTypeId": null
},
"type": "TASK",
"public": true
},
"visible": true,
"expanded": false,
"dashboard": 1121,
"order": 2,
"id": 4555,
"type": "SAVED_VIEW"
},
{
"view": {
"fieldNames": {
"field_37406": "Date",
"field_37417": "Placeholder",
"field_37405": "Signature",
"field_37504": "Map Area",
"field_37416": "Placeholder",
"field_37408": "Date and Time",
"field_37419": "Room Link",
"field_37407": "Time",
"field_37418": "Text",
"field_37409": "Placeholder",
"field_37464": "enter name",
"field_37411": "Multi Select",
"field_37410": "Which step to follow?",
"field_37402": "Text",
"field_37501": "Location",
"field_37413": "Contact Picker",
"field_37412": "Person Picker",
"field_37404": "Number",
"field_37503": "Multiple Locations",
"field_37415": "Task Link",
"field_37403": "Custom Embedded Content",
"field_37502": "Map Line",
"field_37414": "File Attachment"
},
"process": {
"name": "Release 149",
"id": 2325304
},
"containerId": 8133,
"urlExtraInfo": "/processes/2325304",
"lastModifiedBy": {
"name": "Hemi",
"id": 7845
},
"aggregates": {},
"dateModified": "2017-03-22T19:12:52Z",
"viewId": 2337221,
"groupByColumn": null,
"count": 7,
"description": "Workflow",
"shared": false,
"key": "2k5j7avy33",
"properties": {
"originDashboardId": "5823",
"__widgetType": "SAVED_VIEW",
"originContainerId": "8133",
"description": "Workflow",
"showCalendar": "true"
},
"state": {
"vgrid": {
"filters": {
"advancedFilters": {
"displayFilters": [],
"type": "advanced",
"anyAll": "all",
"filters": [
{
"columnId": "field_37406",
"condition": "before",
"value": {
"type": "rel",
"value": -3600000,
"relDate": {
"number": "1",
"units": "hours",
"era": "past"
},
"dateOnly": true
},
"type": "date"
}
]
}
},
"sortCol": "lastModified",
"sortDir": false,
"groupBy": null,
"columns": [
{
"id": "checkedForUpdate",
"hidden": false,
"width": 30
},
{
"id": "editProcessInvocation",
"hidden": false,
"width": 40
},
{
"id": "orgSequenceId",
"hidden": false,
"width": 80
},
{
"id": "requester",
"hidden": false,
"width": 120
},
{
"id": "currentSteps",
"hidden": false,
"width": 180
},
{
"id": "previousSteps",
"hidden": false,
"width": 180
},
{
"id": "wfProgress",
"hidden": false,
"width": 90
},
{
"id": "created",
"hidden": false,
"width": 170
},
{
"id": "lastModified",
"hidden": false,
"width": 170
},
{
"id": "lastModifiedBy",
"hidden": false,
"width": 120
},
{
"id": "duration",
"hidden": false,
"width": 120
},
{
"id": "container",
"hidden": false,
"width": 120
},
{
"id": "stepOwner",
"hidden": false,
"width": 180
},
{
"id": "field_37402",
"hidden": false,
"width": 180
},
{
"id": "field_37403",
"hidden": false,
"width": 180
},
{
"id": "field_37404",
"hidden": false,
"width": 180
},
{
"id": "field_37405",
"hidden": false,
"width": 180
},
{
"id": "field_37406",
"hidden": false,
"width": 180
},
{
"id": "field_37407",
"hidden": false,
"width": 180
},
{
"id": "field_37408",
"hidden": false,
"width": 180
},
{
"id": "field_37501",
"hidden": false,
"width": 180
},
{
"id": "field_37502",
"hidden": false,
"width": 180
},
{
"id": "field_37503",
"hidden": false,
"width": 180
},
{
"id": "field_37504",
"hidden": false,
"width": 180
},
{
"id": "field_37410",
"hidden": false,
"width": 180
},
{
"id": "field_37411",
"hidden": false,
"width": 180
},
{
"id": "field_37412",
"hidden": false,
"width": 180
},
{
"id": "field_37413",
"hidden": false,
"width": 180
},
{
"id": "field_37464",
"hidden": false,
"width": 180
},
{
"id": "field_37414",
"hidden": false,
"width": 180
},
{
"id": "field_37415",
"hidden": false,
"width": 180
},
{
"id": "field_37418",
"hidden": false,
"width": 180
},
{
"id": "field_37419",
"hidden": false,
"width": 180
}
],
"aggregates": [],
"showExactDates": true
}
},
"type": "INVOCATION",
"public": true
},
"visible": true,
"expanded": false,
"dashboard": 1121,
"order": 3,
"id": 22663,
"type": "SAVED_VIEW"
},
{
"view": {
"createdBy": {
"name": null,
"id": 7845
},
"dateCreated": "2017-03-22T19:30:01Z",
"properties": {
"widgetName": "",
"originDashboardId": "5823",
"originContainerId": "8133",
"center": {
"address": {
"formattedAddress": "Gujarat 394310, India",
"postalCode": "394310",
"adminDistrict": "Surat",
"countryRegion": "GJ",
"country": "IN"
},
"point": {
"coordinates": {
"x": 21.1833231,
"y": 73.00510550000001
}
}
},
"weatherOpts": {
"satellite": 1,
"temperature": 1,
"stormReports": 1,
"stormTracks": 1,
"severeWarnings": 1,
"bulletins": 1
},
"zoom": "100",
"widgetType": "WEATHER"
},
"type": "WEATHER"
},
"visible": true,
"expanded": false,
"dashboard": 1121,
"order": 4,
"id": 22664,
"type": "WIDGET"
},
{
"view": {
"createdBy": {
"name": null,
"id": 7845
},
"dateCreated": "2017-03-22T19:41:15Z",
"properties": {
"formId": "2325288",
"tileLabel": "",
"widgetParentContainerId": "1632",
"originDashboardId": "5823",
"originContainerId": "8133",
"formName": "Release 149",
"viewKey": "0",
"containerId": "8133",
"widgetContainerId": "8133"
},
"type": "FORM_ENTRY"
},
"visible": true,
"expanded": false,
"dashboard": 1121,
"order": 5,
"id": 22670,
"type": "WIDGET"
},
{
"view": {
"createdBy": {
"name": null,
"id": 38677
},
"dateCreated": "2018-02-05T14:52:50Z",
"properties": {
"widgetParentContainerId": "1632",
"links": [
{
"label": "Launch Plan: Plan Creation 11 Jan",
"url": "",
"linkAction": "LAUNCH_PLAN",
"newWindow": false,
"container": "9741",
"group": 1632,
"org": "1",
"dynamicContainer": false,
"dynamicParentContainer": false,
"linkObject": "9769",
"plan": {
"id": "9769",
"name": "Plan Creation 11 Jan",
"desc": "By Naveen",
"open": false,
"mainRoomId": "9770",
"mainRoomName": "Room Template Name for Plan Launch",
"category": "Testing Plan",
"planType": "INCIDENT_RESPONSE",
"eventXDate": null,
"launchMessage": "Plan Launch Message by naveen for testing",
"launchable": true,
"progressTracker": null,
"subCategory": "Plan Test",
"canLaunch": true,
"org": "1",
"group": "1632"
}
}
],
"widgetContainerId": "9741",
"tileName": "Links"
},
"type": "LINKS"
},
"visible": true,
"expanded": false,
"dashboard": 1121,
"order": 6,
"id": 27620,
"type": "WIDGET"
}
]
}
""" |
Thanks. It should be possible to get something to work using a serializer plugin: https://github.com/google/built_value.dart/blob/master/built_value/lib/serializer.dart#L141 In the something like:
your aim is to get the So what you'd then have is one piece of code for your whole codebase which figures out the discriminator. Once you have this we can try to figure out a reasonable way to encode that in the individual classes instead of in one place. |
@davidmorgan I was actually looking at the plugin code/documentation the other day and was thinking of doing something like that. Much appreciated, I will let you know when I figure it out 👍 |
@davidmorgan I wanted to let you know that I was able to make it work with a plugin... here's my plugin code DiscriminatorPlugin.dart import 'package:built_value/serializer.dart';
import 'package:veoci/data_model/dashboard_model.dart';
class DiscriminatorPlugin extends SerializerPlugin {
@override
Object afterDeserialize(Object object, FullType specifiedType) {
return object;
}
@override
Object afterSerialize(Object object, FullType specifiedType) {
return object;
}
@override
Object beforeDeserialize(Object object, FullType specifiedType) {
if (specifiedType.root == Tile) {
var map = object as Map;
var type = TileType.valueOf(map['type'] as String);
switch (type) {
case TileType.SAVED_VIEW:
map[r'$'] = 'SavedViewTile';
break;
case TileType.ROOM_NOTE:
map[r'$'] = 'RoomNoteTile';
break;
case TileType.WIDGET:
map[r'$'] = 'WidgetTile';
print('WidgetTile $map');
break;
}
} else if (specifiedType.root == WidgetView) {
var map = object as Map;
var type = WidgetTileType.valueOf(map['type'] as String);
var properties = map['properties'] as Map;
switch (type) {
case WidgetTileType.WEATHER:
properties[r'$'] = 'WeatherTileProperties';
break;
case WidgetTileType.FORM_ENTRY:
properties[r'$'] = 'FormEntryTileProperties';
break;
case WidgetTileType.LINKS:
properties[r'$'] = 'LinksTileProperties';
break;
}
} else if (specifiedType.root == Link) {
var map = object as Map;
var type = LinkAction.valueOf(map['linkAction']);
print('Link $type $map');
switch (type) {
case LinkAction.LAUNCH_PLAN:
map[r'$'] = 'LaunchPlanLink';
break;
}
} else {
print('Discriminator Plugin: ${specifiedType.root} $object');
}
return object;
}
@override
Object beforeSerialize(Object object, FullType specifiedType) {
return object;
}
} I wonder if it's possible as apart of the generator to generate a static function on the extension of @override
beforeDeserialize(Object object, FullType specifiedType) {
if (specified.root is HasDiscriminator) {
return specified.root.discriminate(object, type: specifiedType);
}
return object;
} The above is probably wrong, but does describe what would be nice. |
Thanks! I'm glad it worked. We would need to somehow gather the discriminator information from classes and make it available statically. Not sure how best to do that. I'll give it some thought. I think most likely though this request will hang around until more requests for something similar come in--then we'll have more to go on. |
@davidmorgan makes complete sense. |
Hey, Pretty much running into the same problem. Getting BIG INT identities from the wire that needs to be serialized into a concrete implementation of an identity interface. Thanks @jpgilchrist , I was able to use custom plugin code as well to work around this. Please checkout the usage example and let me know what you think. |
I'm running in a similar issue, I posted about it on stackoverflow : https://stackoverflow.com/questions/55816076/deserialize-generic-type-with-built-value I tried to use the plugin solutions without success. Here's what I tried to do :
Using I did a lot of debugging and found out the error come from I found the place where the I fixed my issue by modifying that method like this :
and adding this in my
That is obviously a temporary hack until I figure out how to fix this issue properly. in case you want to have a look at the json I receive from the api :
In the end I think deserializing generic list is pretty common when dealing with APIs. It would be great to have a written doc about how to do that properly. I haven't found anything online dealing with this |
Hi @jeantuffier , I am having a similar issue and I tried to use your workaround but I believe I am messing up somewhere, would you mind helping out? I can share more details. |
Generics and type inference is a must. Otherwise, we have to write a lot of boilerplate code. I hope it will be implemented very soon. |
@hasan314 I ended up switching to another library that makes it a bit simpler to handle my case. |
You can specify the discriminator field--the field in the JSON which should contain the type name--as an argument to Then, if the string in the JSON does not match your class names, you need to use @BuiltValue(wireName: 'apiComic')
abstract class ApiComic ...
|
I guess I missed the part talking about |
Hi, I have a generic class
and response like I am getting this error I went through all the above comments but couldn't understand how to fix it. |
Try |
I have a rather annoying complex data model that I'm trying to map out with built_value. It's a weird case of Polymorphism where an object contains a list of mixed types. These mixed types (like the animal case) are polymorphic, but I don't know which type it should be until I look at an enum type variable on the object. I see there's the concept of the discriminator field which can be used to determine which serializer to use, but I'm not sure that's quite enough in my situation. It'd be nice to take something like a function to switch on the EnumClass (non nullable) and determine what serializer to use for each object in the list.
It would be interesting to be able to define this function on the generated class that extends Built.
Here's what I have so far, but I think it fails because it doesn't know which type of Tile to instantiate when it deserializes a
Dashboard
.With my suggested feature improvement you could have something like this for the
Tile
class.Unless I'm missing something... Furthermore, if this would be better for SO just let me know and I can move it there.
The text was updated successfully, but these errors were encountered: