Dart Built Value Deserialize List of Objects - dart

I have an API that's returning a list of objects...
[{}, {}, {}, ...]
I already have a defined and working built_value model for each object. However, now I need to deserialize the list.
I currently am trying something like this:
List<Map<String, dynamic>> json = JSON.decode(DEMO_TASK);
json.expand<Task>((Map<String, dynamic> map) => _serializers.deserializeWith<Task>(Task.serializer, map));
However, that causes issues since it says _serializers.deserializeWith return type Task isn't an Iterable<Task> as defined by the closure.
How do I go about deserializing the list. I'm sure I'm missing something super basic.

In case you want to have more general approach you can use this code snippet:
In case anybody needs this functionality I leave here code snippet of how to handle this situation (code should be placed in serializers.dart file):
Serializers standardSerializers = (serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
T deserialize<T>(dynamic value) =>
standardSerializers.deserializeWith<T>(standardSerializers.serializerForType(T), value);
BuiltList<T> deserializeListOf<T>(dynamic value) =>
BuiltList.from(value.map((value) => deserialize<T>(value)).toList(growable: false));
So if you have json file
[
{
"name": "test1",
"result": "success"
},
{
"name": "test2",
"result": "fail"
}
]
And built value class:
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
part 'test_class.g.dart';
abstract class TestClass implements Built<TestClass, TestClassBuilder> {
String get name;
String get result;
TestClass._();
factory TestClass([updates(TestClassBuilder b)]) = _$TestClass;
static Serializer<TestClass> get serializer => _$testClassSerializer;
}
You can use method deserializeListOf from above as:
import 'package:path_to_your_serializers_file/serializers.dart';
final BuiltList<TestClass> listOfTestClasses = deserializeListOf<TestClass>(json);

Yup. I missed something basic. I was thinking I was using a stream, but since it's a list you just have to use the .map function on a list.
List<Map<String, dynamic>> json = JSON.decode(DEMO_TASK);
List<Task> tasks = json.map<Task>((Map<String, dynamic> map) => _serializers.deserializeWith<Task>(Task.serializer, map)).toList();

Related

Access variables of classes when looping list of classes and getting an instance of Type

I have this Python code:
def from_json(cls, data: dict, api: Optional[Overpass] = None) -> "Result":
"""
Create a new instance and load data from json object.
:param data: JSON data returned by the Overpass API
:param api:
:return: New instance of Result object
"""
result = cls(api=api)
elem_cls: Type[Union["Area", "Node", "Relation", "Way"]]
for elem_cls in [Node, Way, Relation, Area]:
for element in data.get("elements", []):
e_type = element.get("type")
if hasattr(e_type, "lower") and e_type.lower() == elem_cls._type_value:
result.append(elem_cls.from_json(element, result=result))
return result
and I want to convert this to Dart code. This is what I've come up with:
factory Result.fromJson(Map<String, dynamic> data, {Overpass? api}) {
Result result = Result(api: api!);
Type elemCls;
for (elemCls in [Node, Way, Relation, Area]) {
for (Map<String, dynamic> element in data["elements"]) {
String eType = element["type"];
if (eType.toLowerCase() == elemCls.typeValue) {
result.append(elemCls.fromJson(element, result: result));
}
}
}
return result;
}
My problems are The getter '_typeValue' isn't defined for the type 'Type'. and The method 'fromJson' isn't defined for the type 'Type'.. This is because _typeValue and fromJson belong to the classes Node, Way, Relation and Area and not to Type which is what I get with this for loop. What do I have to change to get this working like in Python?
You could do eType.toLowerCase() == elemCls.toString().toLowerCase(). However, elem_cls.from_json(element, result=result) cannot be directly done in Dart. There is no way to generically instantiate an object with type elemCls.
What you instead could do is create a lookup table to map type names to constructors:
final _constructionTable = {
"node": Node.fromJson,
"way": Way.fromJson,
"relation": Relation.fromJson,
"area": Area.fromJson,
};
factory Result.fromJson(Map<String, dynamic> data, {Overpass? api}) {
Result result = Result(api: api!);
for (Map<String, dynamic> element in data["elements"]) {
String eType = element["type"]!;
var fromJson = _constructionTable[eType.toLowerCase()];
if (fromJson != null) {
result.append(fromJson(element, result: result));
}
}
return result;
}
Note that this also would be a bit more efficient too, and it avoids relying on the specific strings generated for the Dart types.

Dart null safety and parsing JSON to models with json_serializable

I have a very general question after almost one week of playing around with various tools to de-serialize JSON to Dart and being very frustrated about null safety. The json_serializable package which generates .fromJson and .toJson messages should care about NULL values. But I could not make that work at all! So the one million dollar question is:
Do I have to declare ALL properties of a model as NULLABLE (with a ? e.g. String? myString) if there is any chance that the property may be missing or NULL in the returned JSON ??
When I do NOT declare such members as NULLABLE, I always get this error:
Unhandled Exception: type 'Null' is not a subtype of type 'List<dynamic>' in type cast
These errors always occur in the generated FromJson methods. Here is a sample JSON which causes the problem:
"responseStatus": {
"errorCode": "500",
"message": "This is a test message",
"errors": [
{
"errorCode": "300",
"fieldName": "a field name",
"message": "a message",
"meta": {
"a key": "a value"
}
}
],
"meta": {
"some field": "some value"
}
}
The errors array contains an array of error objects which itself may or may not contain an array of key value pairs (meta). If there are NO errors, the errors array is not submitted as shown here:
"responseStatus": {
"errorCode": "500",
"message": "This is a test message",
"meta": {
"some field": "some value"
}
In such a case the generated DART code crashes as shown below:
ResponseStatus _$ResponseStatusFromJson(Map<String, dynamic> json) =>
ResponseStatus(
errorCode: json['errorCode'] as String? ?? '',
message: json['message'] as String? ?? '',
stackTrace: json['stackTrace'] as String? ?? '',
)
..errors = (json['errors'] as List<dynamic>) // <<==== This is crashing if errors is not in the returned JSON
.map((e) => ResponseError.fromJson(e as Map<String, dynamic>))
.toList()
..meta = Map<String, String>.from(json['meta'] as Map);
The model class I created looks as follows:
#JsonSerializable(explicitToJson: true)
class ResponseStatus extends Equatable {
late final String errorCode;
late final String message;
late final String stackTrace;
late final List<ResponseError> errors;
late final Map<String, String> meta;
ResponseStatus({this.errorCode = '', this.message = '', this.stackTrace = ''}) {
this.errors = [];
this.meta = Map();
}
factory ResponseStatus.fromJson(Map<String, dynamic> json) =>
_$ResponseStatusFromJson(json);
Map<String, dynamic> toJson() => _$ResponseStatusToJson(this);
#override
List<Object?> get props => [errorCode, message, stackTrace, errors, meta];
}
I created a default constructor which initializes all the members (which are NON-nullable) with a value.
If I have to declare all model members as nullable this would be unusable since in my flutter widgets I would have to write thousands of if statements to check if a member is null. I think to avoid this was ONE of the ideas behind sound null safety.
I am curious to learn if this is a bug or how I get this thing working!
This is expected behaviour. If data is not given, a crash must occur, because a non-nullable variable can never have a null value.
You can, however, use a custom deserialisation function to handle this case:
#JsonKey(fromJson: optionalErrorListFromJson)
late final List<ResponseError> errors;
List<ResponseError> optionalErrorListFromJson(List<dynamic>? errors) {
if (errors == null) return const [];
return errors
.map((e) => ResponseError.fromJson(e as Map<String, dynamic>))
.toList(growable: false)
}

Dart: Maps nested in maps

I want to store various data for my app in a single place, in a map. In JS, I'd store in a JSON file, and I want to use the same sort of approach, but struggling with Dart. I can't seem to work with nested lists or maps.
Here's essentially what I want to do:
var items = {
"item1": {
"message" : "aa",
"nested1": {
"message": "bb",
"nested2" : {
"message" : "cc"
},
}
},
};
void main() {
var message1 = items["item1"]?["message"];
print(message1);
print(message1.runtimeType);
var message2 = items["item1"]?["nested1"]?["message"];
print(message2);
print(message2.runtimeType);
var message3 = items["item1"]?["nested1"]?["nested2"]?["message"];
print(message3);
print(message3.runtimeType);
}
I've been struggling to make this work in Dartpad.
message1 works as expected, but then I can't seem to work my way down the tree...
Is this a shortcoming with map literals? Do I need to use constructors? Or am I missing something bigger?
Your problem is that items is inferred to be of type Map<String, Map<String, Object>>, but Object does not have an operator []. Therefore when you eventually extract that Object, you will not be able to do anything with it until you cast it a more specific type.
What you probably want instead is to explicitly declare items as Map<String, dynamic> to disable static type-checking on the Map's values:
var items = <String, dynamic>{
"item1": ...
};
Of course, when you disable static type-checking, you are responsible for ensuring that the values you get from the Map are what you expect, or you will get NoSuchMethod or TypeError exceptions at runtime. If you do want static type-checking, you should use define custom classes instead of using a blob of key-value properties.

How to convert Class object to data structure `Map` or a `List of Maps` in Dart?

How to convert an Object type to a Map or a List of Maps in Dart, so the variables become key/value pairs?
Based on my experience, dart does not provide that kind of system yet. So, basically we create function like toMap() that manually convert the object to a key-value pair of map.
For example:
class Human {
String name;
int age;
Map<String, dynamic> toMap() {
return {
'name': name,
'age': age,
};
}
}
So, later when you have a Human object, you just can call human.tomap().
I do this in most of my entity classes.
This is a much easier and error-prone-free approach, (And if you are already using these classes as DTO objects that need to be serialized this is a double win).
Add the following 3 dependenciess in your pubsec.yaml
dependencies:
flutter:
sdk: flutter
json_annotation:^3.1.1
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.10.11
json_serializable: ^3.2.5
In the class that you want to get the Map for do the following.
part 'person.g.dart';
#JsonSerializable(explicitToJson: true)
class Person{
String name;
String surname;
Person(this.name,this.surname);
//make sure you have the (Just change part of the method name to your class name)
factory Person.fromJson(Map<String, dynamic> json) =>
_$PersonFromJson(json);
Map<String, dynamic> toJson() => _$PersonToJson(this);//Replace 'Person' with your class name
}
That's it, you then just have to run the following command in your terminal so flutter can generate the "toMap()" method for you.
flutter pub run build_runner build --delete-conflicting-outputs
Then use it like such.
var person =Person('somename','some surname');//This is object
Map personMap =person.toJson();
Ez.
This is a much better approach especially if you have complex objects with multiple objects inside. Just make sure that each nested object contains these changes as well (JsonSerializable annotation, the 'part 'class.g.dart'' , and the 2 methods)
NOTE: that if property changes are made to the classes, just run this command again.
flutter pub run build_runner build --delete-conflicting-outputs
You can check out this package class_to_map
import 'package:class_to_map/class_to_map.dart';
class Test {
String val1 = "value 1";
int val2 = 2;
Map val3 = {"a": "another value"};
}
print(Test().toMap());
// converting map to class
{"val1": 'value 1', "val2": 2, "val3": {"a": "another value"} }.toClass<Test>();
You can try this:
var data = {
"key1": "value1",
"key2": "value2",
"key3": "value3",
};
var myMap = Map<String, dynamic>.from(data);
print(myMap);
With dynamic in Map<String, dynamic> we can have the values of the map of any type. In the example above, it is String.

How do I call a method from an extended class?

i have no experience with extended classes
so don't be shocked... that's what I got:
the 'basic class' I want to extend in my models
to avoid repeat fromJson/toJson every 2 lines
import 'dart:convert';
class BaseModel {
Map<String, dynamic> json2Map(String json) => jsonDecode(json);
String map2Json(Map<String, dynamic> map) => jsonEncode(map);
json2List(String jsonList) {
List<Map<String, dynamic>> _list = [];
jsonDecode(jsonList).forEach((_json) => _list.add(jsonDecode(_json)));
return _list;
}
mapList2Json(List<Map<String,dynamic>> list) {
List<String> jsonList= [];
list.forEach((_map) => jsonList.add(map2Json(_map)));
return jsonEncode(jsonList);
}
}
and here is one of the class that extends this:
import 'package:bloc_hub/models/base_model.dart';
class Info extends BaseModel {
final String name;
final String company;
Info({this.name,this.company});
factory Info.fromMap(Map<String, dynamic> json) => new Info(
name: json['name'],
company: json['company'],
);
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
map['name'] = name;
map['company'] = company;
return map;
}
}
(I'm in a streambuilder and client.info is a json)
then... when I try to call 'json2map'
which is from the extended class...
Info info = Info.fromMap(json2Map(client.info));
i get this:
[dart] The method 'json2Map' isn't defined for the class 'ListPage'. [undefined_method]
what did I get wrong?
if I wasn't clear don't refrain to ask me anything
thank you for your help
[edit: bonus question
how a mixin is different from what I'm doing?]
json2Map is an instance method of BaseModel, so in order to call it you must use an instance of BaseModel or a class that extends it (like Info), like:
var b = new BaseModel();
b.json2Map(something);
The error message says you're calling it from ListPage, so the method is not found.
Alternatively, you could make the methods static and call it like BaseModel.json2Map(...) (without an instance).
There are some good explanations about mixins here (with Python examples, but the concepts are the same for Dart). I guess in your example it would make more sense to have a mixin with JSON related functions, since you could use them in other kind of objects.

Resources