Serialize Dart Map - flutter

What is the best way to unmarshall this json in dart:
{
"Ace-spades-2": {
"rank": { "shortName": "A", "longName": "Ace" },
"suit": { "name": "spades" },
"id": "Ace-spades-2"
},
"Two-spades-2": {
"rank": { "shortName": "2", "longName": "Two" },
"suit": { "name": "spades" },
"id": "Two-spades-2"
},
"Three-spades-2": {
"rank": { "shortName": "3", "longName": "Three" },
"suit": { "name": "spades" },
"id": "Three-spades-2"
},
{....a bunch more cards}
}
Here are my classes that I want to unmarshall into. Specifically I want to make the data Map<String,Card>. How can I do this?
class Card {
Rank rank;
Suit suit;
String id;
Card({this.rank,this.suit,this.id})
}
class Rank {
String shortName;
String longName;
Rank({this.shortName, this.longName});
}
class Suit {
String name;
Suit({this.name});
}
In Go, I would just do
cards := map[string]Card{}
json.Unmarshal(<some_json>, &cards).
What is the best way to do this in dart?

For serialization in Flutter (where reflection is not available) is very common to use the json_serializable package. A good tutorial about this package is also in the flutter doc: https://flutter.dev/docs/development/data-and-backend/json#serializing-json-using-code-generation-libraries
When you have annotated yours classes (and you have completed the others installation instructions - also remember the annotation on the library declaration, which is mandatory), you can take advantage of the Card.fromJson() factory constructor. You can convert your json raw String in a Map<String, dynamic> using the jsonDecode() function (of the dart:convert package), then iterate on the Map and convert every entry in the Map with Card.fromJson.
For example:
Map<String, Card> cardMap = (jsonDecode(jsonString) as Map<String, dynamic>).map((key, cardString) => MapEntry(key, Card.fromJson(cardString)));
A very important note: when you have a class (Card and Rank, in your example) with fields whose type is not primitive, but instead represents another class annotated with JsonSerializable, the JsonSerializable annotation must pass explicitToJson: true (i.e. #JsonSerializable(explicitToJson: true) class Card).
The process to serialize class/deserialize json the first time isn't as fast as in Go, but flutter doesn't have reflection.

Related

How can I manipulate JSON data in dart / flutter for use in a listview

Background
I have the following JSON data that is being remotely retrieved via an async request, that I'm trying to build a list view out of in Flutter.
As you can see the first item is 'logged in' which is totally different from all the other items
I'm having a lot of trouble in flutter when trying to build a list view out of the data (I'm a total flutter noob --> This is day 0).
[
{
"loggedin": "0"
},
{
"id": "1",
"title": "Title 1",
"excerpt": "",
"thumb": "Image 1.jpg",
"threadid": "1",
"fid": "1",
"commentcount": "1",
"postdate": 1
}, {
"id": "2",
"title": "Title 2",
"excerpt": "",
"thumb": "Image 2.jpg",
"threadid": "2",
"fid": "2",
"commentcount": "2",
"postdate": 2
}, {
"id": "3",
"title": "Title 3",
"excerpt": "",
"thumb": "Image3.jpg",
"threadid": "3",
"fid": "3",
"commentcount": "3",
"postdate": 3
}
]
My Conceptual Solution
I was thinking of stripping out the first item logged in and forming a whole new Json array with just Items 1-3
My Question
Is it possible to iterate through the decoded json data and form a new array?
I can successfully access an individual item in my list as follows:
_map = json.decode(response.body)[1];
However when I try to iterate through my list it fails
final decoded = json.decode(response.body) as dynamic;
decoded.forEach((key, value) {
if (key != "loggedin") {
debugPrint('hi');
}
});
If I try to iterate through just one of the items then it does work:
final decoded = json.decode(response.body)[1] as dynamic;
decoded.forEach((key, value) {
debugPrint(key+': '+value);
});
I needed to iterate through my list as a starting point to pick out the items I want to remove from my final list (basically the item with a key of "loggedin", but I'm failing miserably here
Alternatives
I realize the very concept of my approach is most likely flawed or tedious. If any other alternative approach to achieve my goal seems better I'm all ears! :)
The error's happening because your data is a list of maps but in the list iterator you are passing a function with two parameters, which works for a map.
You should do it like this -
final decoded = json.decode(response.body) as dynamic;
decoded.forEach((data) {
//data is a Map
});
You can access the individual keys like this - data["loggedin"]
To check whether it contains 'loggedin', you can do data.containsKey("loggedin")
For a list, the function in the forEach takes one Argument which is a list item, and for a Map it takes two, a key and its corresponding value.
I'd recommend creating a Model class with the below fields:
id
title
excerpt
thumb
threadid
fid
commentcount
postdate
loggedin
Now, for the first item, all fields will be null except loggedin and for other items loggedin will be null.
Convert response to List<Map>, iterate through list, ignore item that contains keys loggedin
final List<Map> list = json.decode(response.body);
list.forEach((element) {
if(!element.containsKey("loggedin")){
//Here element is map of your required item
//You can convert it to your required model
}
});

Insert map key as an id inside map value

My goal is to insert a map key as an id inside map value (which is also a map, the nested one).
Say I have a map as follows
Map<String, dynamic> userMap = {
"id_1" : {
"name": "John",
"surname": "Doe",
"age": 25,
},
"id_2" : {
"name": "Jeremy",
"surname": "Smith",
"age": 42,
}
};
I want to convert this to
Map<String, dynamic> userMap = {
"id_1" : {
"id": "id_1",
"name": "John",
"surname": "Doe",
"age": 25,
},
"id_2" : {
"id": "id_2",
"name": "Jeremy",
"surname": "Smith",
"age": 42,
}
};
I tried this
userMap.entries.map((entry) => (entry.value as Map).putIfAbsent('id', () => entry.key));
but it is not working. Instead it is giving result as (id_1, id_2). What I am doing wrong ?? Is there any way to achieve the desired one ??
userMap.entries.map((entry) => (entry.value as Map).putIfAbsent('id', () => entry.key));
The reason why that doesn't work is because you don't do anything with the result of calling .map(). From the Iterable.map documentation:
Returns a new lazy Iterable.... As long as the returned Iterable is not iterated over, the supplied function f will not be invoked.
Your callback function thus is never called and will not mutate the original Map. You could forcibly iterate over the result of .map() (e.g. userMap.entries.map(...).toList()) to get your desired result. However that's wasteful and misuses Iterable.map.
Iterable.map is meant to be used to perform a functional transformation on an Iterable; you should not use it when the callback has side effects. (A "side effect" is something that the callback does other than just returning a value.) For callbacks where you care about side effects, either use Iterable.forEach or use a normal for loop.
You can use this:
userMap.forEach((k,v) => {(v as Map).putIfAbsent('id', () => k)});

How to convert inner json part into object in dart/flutter

I need to convert every element of 'content' array to object.
Here is json:
{
"content": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"author": {
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
},
"createDate": "2020-01-30T20:18:29.764Z",
"executor": {
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
},
}
],
"first": true,
"numberOfElements": 0,
}
The problem is that 'content' array is inside json, and its parts as executor and author has to be objects too, and I don't know how to reach it and parse.
How it can be done? Any help, thanks.
You access the elements like this:
var decoded = json.decode(j);
var inner = decoded['content'][0]; // if you expect more than one entry, iterate the list
print(inner['id']); // -> 3fa85f64-5717-4562-b3fc-2c963f66afa6
print(inner['createDate']);
print(inner['author'].runtimeType); // another Map<String, dynamic> as expected
print(inner['author']['userId']);
You can create Dart classes to model, for example, a 'user' if you want.

spring-data-mongodb document design options - array vs dynamic field (i.e. map)

I am designing a document structure and use spring-data-mongodb to access it. The document structure is to store device profile. Each device contains modules of different types. The device can contains multiple modules of the same type. Module types are dynamic as new type of modules are created sometimes.
Please note: I try not to write custom queries to avoid boilerplate code. But, some custom queries should be fine.
I come out with two designs:
the first one use dynamic field (i.e. map). Semantics is better but seems harder to query/update using spring-data-mongodb.
{
deviceId: "12345",
instanceTypeMap: {
"type1": {
moduleMap: {
"1": {field1: "value",field2: "value"},
"2": {field1: "value",field2: "value"}
}
},
"type2": {
moduleMap: {
"30": {fielda: "value",fieldb: "value"},
"45": {fielda: "value",fieldb: "value"}
}
}
}
the second one use array and query/update seems more in-line with spring-data-mongodb.
{
deviceId: "12345",
allInstances: [
{
type: 1,
modules: [
{
id: 1,
field1: "value",
field2: "value"
},
{
id: 2,
field1: "value",
field2: "value"
}
]
},
{
type: 2,
modules: [
{
id: 30,
fielda: "value",
fieldb: "value"
},
{
id: 45,
fielda: "value",
fieldb: "value"
}
]
}
]
}
I am inclined to use array. Is it better to use array instead of dynamic field with spring-data-mongodb. I did some search on-line and found people mentioned that query for key (i.e. in map) is not as easy in spring-data-mongodb. Is that a correct statement? Do I miss anything? Thank you in advance.
I ended up with the design as below. I use one device-instance-type per document. Because, in some scenario,
updates are done on many modules of the same instance type. Those updates can be aggregated as just one database update.
The redundant "moduleId" field is also added for query purpose.
{
deviceId: "12345",
instanceTypeId: "type1",
moduleMap: {
"1": {
moduleId: "1",
field1: "value",
field2: "value"
},
"2": {
moduleId: "2",
field1: "value",
field2: "value"
}
}
}
Now, I can use spring-data-mongodb's query:
findByDeviceId("12345");
findByDeviceIdAndInstanceTypeId("12345","type1");
findByDeviceIdAndInstanceTypeIdAndModuleMapModuleId("12345","type1","1");

[json4s]:Extracting Array of different objects

I am using the facebook graph API and the responses look similar to this:
{
"data": [
{
"id": "311620272349920_311718615673419",
"from": {
"id": "1456046457993048",
"name": "Richard Ettinson"
},
"to": {
"data": [
{
"id": "311620272349920",
"name": "Barbara Fallerman"
}
]
},
"with_tags": {
"data": [
{
"id": "311620272349920",
"name": "Barbara Fallerman"
}
]
},
"message": "I was gong out with her",
"actions": [
{
"name": "Comment",
"link": "https://www.facebook.com/311620272349920/posts/311718615673419"
},
{
"name": "Like",
"link": "https://www.facebook.com/311620272349920/posts/311718615673419"
}
]
}
I managed to for example extract the from field through
val extracted = (json \ "data" \"from").extract[PostFrom]
But I worry that if I use this technique I will need to pass over the Json multiple times to extract all the values I need which could lead to a bad performance.
How exactly could I extract these fields into case classes from the array of non similar objects?
I tried with the following case classes:
abstract class BaseResponse()
case class Data(list:List[Post])
case class Post(id: String, post: PostFrom) extends BaseResponse
case class PostFrom(id: String, name:String)
Which always lead to an empty List, is there a way to get back a Data class which has a list of certain classes which I am interested in? (As example the top level id, from and with_tags)
A possibility I found was to use more case classes instead of inheritance:
case class Root[T](data:Option[T])
case class Post(id: String, from: From, message: String)
case class From(id: String, name:String)
Basically there has to be a root object which takes some kind of graphs response object, additionally it is optional so that it won't throw an exception if there was a problem with the parsing of the response.
I then used it in the following way:
val body = r.entity.asString
val json = parse(r.entity.asString)
val root = json.extract[Root[Post]]
root.data match {
case Some(post) =>
val tagger = Tagger(post.from.id, post.from.name, post.id, post.message)
log.info(s"We received $tagger")
originalSender ! RetrievedTagger(tagger)
case None => originalSender ! NoTaggerFound
}