How to encode an object to json in Flutter - json

I am trying to convert the object "Week" to json.
https://flutter.dev/docs/development/data-and-backend/json this is the source that i used
class Week{
DateTime _startDate;
DateTime _endDate;
List<Goal> _goalList;
String _improvement;
Week(this._startDate, this._endDate){
this._goalList = List<Goal>();
this._improvement = "";
}
Week.fromJson(Map<String, dynamic> json)
: _startDate = json['startDate'],
_endDate = json['endDate'],
_goalList = json['goalList'],
_improvement = json['improvement'];
Map<String, dynamic> toJson() =>
{
'startDate': _startDate,
'endDate': _endDate,
'goalList': _goalList,
'improvement': _improvement,
};
}
I used this:
DateTime startDate = currentDate.subtract(new Duration(days:(weekday-1)));
DateTime endDate = currentDate.add(new Duration(days:(7-weekday)));
Week week = new Week(startDate, endDate);
var json = jsonEncode(week);
But the problem is that I get this result:
Unhandled Exception: Converting object to an encodable object failed: Instance of 'Week'
#0 _JsonStringifier.writeObject (dart:convert/json.dart:647:7)
#1 _JsonStringStringifier.printOn (dart:convert/json.dart:834:17)
#2 _JsonStringStringifier.stringify (dart:convert/json.dart:819:5)
#3 JsonEncoder.convert (dart:convert/json.dart:255:30)
#4 JsonCodec.encode (dart:convert/json.dart:166:45)
#5 jsonEncode (dart:convert/json.dart:80:10)

jsonEncode requires a Map<String, dynamic>, not a Week object. Calling your toJson() method should do the trick.
var json = jsonEncode(week.toJson());
However, keep in mind that your toJson() method is also incorrect, as things like _goalList and the dates are still objects, not Maps or Lists. You'll need to implement toJson methods on those as well.
To answer your specific questions:
Because dart is not javascript / typescript. Dart checks types at runtime, therefore you have to explicitly tell it how to convert things - also there is no reflection in dart, so it can't figure it out by itself.
You can use a library that uses code generation to do these things automatically for you - it still wont be possible at runtime though - read more about JSON serialization.
The easiest way would be to implement the methods directly in the classes, as that's where you have access to in your root object. Keep in mind that the structure that jsonEncode needs is a Map<String, dynamic>, but the dynamic part really means List<dynamic>, Map<String, dynamic> or a primitive that is json compatible such as String or double - if you try to imagine how such a nested structure of said types looks, you'll realise that it's basically json. So when you do something like 'goalList': _goalList, you're giving it an object, which is not one of the allowed types.
Hope this clears things up a bit.

for anyone wondering: I got my solution.
To make my code work I needed to implement the toJson() methods at my class Goal as well (because I used List<Goal> in Week).
class Goal{
String _text;
bool _reached;
Map<String, dynamic> toJson() =>
{
'text': _text,
'reached': _reached,
};
}
Also, I needed to add .toIso8601String() to the DateTime objects like that in the Week class:
Map<String, dynamic> toJson() =>
{
'startDate': _startDate.toIso8601String(),
'endDate': _endDate.toIso8601String(),
'goalList': _goalList,
'improvement': _improvement,
};
Now the output is:
{"startDate":"2019-05-27T00:00:00.000Z","endDate":"2019-06-02T00:00:00.000Z","goalList":[],"improvement":""}

Taking suggestion 2 from #Phillip's answer above for Json serialization, the Freezed package I believe you can skip the #JsonSerializable annotation and just used the #Freezed annotation because Freezed "will automatically ask json_serializable to generate all the necessary fromJson/toJson."
So the example here: https://flutter.dev/docs/development/data-and-backend/json#use-code-generation-for-medium-to-large-projects
becomes:
//import 'package:json_annotation/json_annotation.dart';
import 'freezed_annotation/freezed_annotation.dart';
/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';
part 'user.freezed.dart';
/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
#freezed
class User {
User(this.name, this.email);
String name;
String email;
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Freezed: https://pub.dev/packages/freezed
Don't forget to edit pubspec.yaml for freezed and freezed_annotation

Related

how #JsonKey(toJson: toJSON_SalesInvoiceDetail,) function works in flutter Jsonserializable

I am new to this serialization, I am facing a hard time understanding the difference between these two functionalities while serialization in dart/flutter #JsonKey(name: invoice) && #JsonKey(toJson: toJSON_SalesInvoiceDetail)
I see my friend wrote this below function outside the class for the above json key #JsonKey(toJson: toJSON_SalesInvoiceDetail) as I see this maps another class so it's kind of embedding one class into another but I do not know how this serialization is happening under the hood, could anyone guide me about this pls, Your help will be appreciated a lot, thanks in advance
The param toJson changes the value of JSON(only the method toJson), and the param name changes the key (both of the methods toJson and fromJson).
For example, I made a class like the following.
#JsonSerializable()
class Any {
const Any(this.param);
factory Any.fromJson(Map<String, dynamic> json) => _$AnyFromJson(json);
#JsonKey(name: 'json_key_of_param', toJson: paramToJson)
final int param;
Map<String, dynamic> toJson() => _$AnyToJson(this);
}
String paramToJson(int paramValue) {
return 'param value is $paramValue';
}
And if we use this like
print(const Any(100).toJson());
then printed the following.
{json_key_of_param: param value is 100}
In this case, I didn't specify the param fromJson, so we should use the method fromJson like the following.
Any.fromJson(<String, dynamic>{'json_key_of_param': 100});

Parsing a json in flutter

I have this json that I am fetching from firebase realtime database
[{image: https://cdn.searchenginejournal.com/wp-content/uploads/2019/07/the-essential-guide-to-using-images-legally-online-1520x800.png, shopId: 1},
{image: https://cdn.searchenginejournal.com/wp-content/uploads/2019/07/the-essential-guide-to-using-images-legally-online-1520x800.png, shopId: 2}]
When I try to parse it, I am receiving different errors:
First way:
List<HomeSlider> posts = List<HomeSlider>.from(l.map((model)=> HomeSlider.fromJson(model)));
imgList.addAll(List<HomeSlider>.from(data.value) => HomeSlider.HomeSlider.fromJson(json));
Second way:
Map<dynamic, dynamic> yearMap = data.value;
yearMap.forEach((key, value) {
imgList.add(HomeSlider.fromJson(value));
});
I have the home slider object:
import 'package:json_annotation/json_annotation.dart';
/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'HomeSlider.g.dart';
#JsonSerializable()
class HomeSlider{
HomeSlider(this.image, this.shopId);
String image="";
String shopId="";
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory HomeSlider.fromJson(Map<String, dynamic> json) => _$HomeSliderFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String, dynamic> toJson() => _$HomeSliderToJson(this);
}
Errors are:
'String' is not a subtype of type 'Map<String, dynamic>'
or w json exception if I try to encode it
I have generated the class as by the documentation using flutter CLI
Any suggestions? Thanks
First: Your Json looks wrong, it's missing the double quotes:
[{"image": "https://cdn.searchenginejournal.com/wp-content/uploads/2019/07/the-essential-guide-to-using-images-legally-online-1520x800.png", "shopId": 1},
{"image": "https://cdn.searchenginejournal.com/wp-content/uploads/2019/07/the-essential-guide-to-using-images-legally-online-1520x800.png", "shopId": 2}]
Second:
In your Model shopId probably should be an int.
If string is intended, you also need to put the id in double quotes.
Then use jsonDecode to convert the String into a List<Map<String,dynamic>> and convert it like that:
List<HomeSlider> homeSliderList;
homeSliderList= (jsonDecode(yourJsonString) as List)
.map((i) => HomeSlider.fromJson(i))
.toList();

flutter inheritance with null safety

I am upgrading my flutter project to null safety support. Inside this project I have abstract model classes and inheritances. They have looked like so:
abstract class Technology {
Guid id;
String name;
String description;
String assetName;
Technology();
factory Technology.fromJson(Map<String, dynamic> json) =>
TechnologyConverter().fromJson(json);
Map<String, dynamic> toJson() => TechnologyConverter().toJson(this);
}
abstract class Research extends Technology {
ResearchType researchType;
Research();
factory Research.fromJson(Map<String, dynamic> json) =>
ResearchConverter().fromJson(json);
}
class LevelableResearch extends Research {
LevelableResearch();
factory LevelableResearch.fromJson(Map<String, dynamic> json) =>
_$LevelableResearchFromJson(json);
Map<String, dynamic> toJson() => _$LevelableResearchToJson(this);
}
This was no problem for the build_runner to generate the json serializer.
Now as I updated to null safety, I get a lot of errors, starting with "Non-nullable instance field 'id' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'"
If I initialize the field within the constructor, I have to call ": super()" with all the needed fields from the inherited class, which results in way too much writing.
As the error says, I could mark it with late, but I am not sure if this is the way it should be done.
What is the correct way to write these classes, so the build_runner can generate a correct json serializer?
If I initialize the field within the constructor, I have to call ": super()" with all the needed fields from the inherited class, which results in way too much writing.
That's true only if you modified your constructor to take required arguments. Required arguments naturally prevent derived class's constructors from implicitly calling super() to construct the base class.
In general (i.e., not specifically for the JSON deserialization library you're using), if you don't want to have required arguments for your base class constructor, then you can:
Initialize members to non-null default values, either via your constructor's initializer list or by using inline field initializers.
Mark the members late. Do this only if you can guarantee that the members will be initialized before they are accessed.
Make the members nullable. If you then add null assertions (!) wherever the members are unconditionally accessed, this is equivalent to what your code did before null-safety. (Or add null-checks that fail gracefully.)

Factory and named constructor for json mapping in dart

Consider this code
class Album {
int userId;
int id;
String title;
Album({this.userId, this.id, this.title});
Album.fromJsonN(Map<String, dynamic> json) {
this.userId = json['userId'];
this.id = json['id'];
this.title = json['title'];
}
factory Album.fromJson(Map<String, dynamic> json) {
return Album(userId: json['userId'], id: json['id'], title: json['title']);
}
}
In most tutorials the explanation for why we use factory for json mapping method is: "we use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class".
in factory method in above code, doesn't it returning a new instance? if it does, so whats the reason for using factory here?
and whats the difference between factory constructor and fromJsonN named constructor in this context?
A Dart class may have generative constructors or factory
constructors. A generative constructor is a function that always
returns a new instance of the class. Because of this, it does not
utilize the return keyword.
A factory constructor has looser constraints than a generative constructor. The factory need only return an instance that is the same
type as the class or that implements its methods (ie satisfies its
interface). This could be a new instance of the class, but could also
be an existing instance of the class or a new/existing instance of a
subclass (which will necessarily have the same methods as the parent).
A factory can use control flow to determine what object to return, and
must utilize the return keyword. In order for a factory to return a
new class instance, it must first call a generative constructor.
Please see also Understanding Factory constructor code example - Dart for a very detailed explanation.
So for your question: Yes it is returning a new Instance but i guess the speciality comes from the fact that you the factory constructor is capable of creating an object based on an incoming json map whereas the generative constructor is used to instantiate a new object from single attributes.
And for your last question: Both do the same, namely returning an instance of the class given a json map. The techincal difference is that one is a generative and one a factory constructor.
One of the use cases when to use factory constructor or named constructor.
To initialise the final fields of the class you have to do it in initializer list or in the declaration when using named constructor. On the other hand using factory constructor You can initialise the final fields in the body of the constructor.

How to convert an object containing DateTime fields to JSON in Dart?

I try to convert an object to JSON.
var obj = { "dt": new DateTime.now() };
var s = stringify(obj);
The runtime throws an exception: "Calling toJson method on object failed."
That's expected since DateTime class doesn't have toJson method.
But what should I do in this case?
Javascript's JSON.stringify function has an optional argument replacer which allows me to provide my own way of serialization of any object even if the object has no toJson method. Is there any similar facility in Dart or maybe I can extend somehow DateTime class with my own toJson method?
JSON conversion only works with maps, lists, strings, numbers, booleans, or null. So what if your object contains another type like DateTime?
DateTime → JSON
Let's start with the following object:
class Person {
Person(this.name, this.birthdate);
String name;
DateTime birthdate;
}
You can convert it to a map like this:
final person = Person('Bob', DateTime(2020, 2, 25));
Map<String, dynamic> map = {
'name': person.name,
'birthdate': person.birthdate,
};
If you tried to encode this right now with jsonEncode (or json.encode), you would get an error because the DateTime is not directly serializeable. There are two solutions.
Solution 1
You could serialize it yourself first like this:
Map<String, dynamic> map = {
'name': person.name,
'birthdate': person.birthdate.toIso8601String(),
};
final jsonString = json.encode(map);
Note:
Here is the difference between toString and toIso8601String:
2020-02-25 14:44:28.534 // toString()
2020-02-25T14:44:28.534 // toIso8601String()
The toIso8601String doesn't have any spaces so that makes it nicer for conversions and sending over APIs that might not deal with spaces well.
Solution 2
You could use the optional toEncodable function parameter on jsonEncode.
import 'dart:convert';
void main() {
final person = Person('Bob', DateTime(2020, 2, 25));
Map<String, dynamic> map = {
'name': person.name,
'birthdate': person.birthdate,
};
final toJson = json.encode(map, toEncodable: myDateSerializer);
}
dynamic myDateSerializer(dynamic object) {
if (object is DateTime) {
return object.toIso8601String();
}
return object;
}
The toEncodable function just converts the input to a string or something that jsonEncode can covert to a string.
JSON → DateTime
There is nothing special here. You just have to parse the string into the type that you need. In the case of DateTime you can use its parse or tryParse methods.
final myMap= json.decode(jsonString);
final name = myMap['name'];
final birthdateString = myMap['birthdate'];
final birthdate = DateTime.parse(birthdateString);
final decodedPerson = Person(name, birthdate);
Note that parse will throw an exception if the format of the string cannot be parsed into a DateTime object.
As a model class
class Person {
Person(this.name, this.birthdate);
String name;
DateTime birthdate;
Person.fromJson(Map<String, dynamic> json)
: name = json['name'],
birthdate = DateTime.tryParse(json['birthdate']),
Map<String, dynamic> toJson() {
return {
'name': name,
'birthdate': birthdate.toIso8601String(),
};
}
}
This will not throw an exception is the date is malformatted, but birthdate would be null.
Notes
See my fuller answer here.
Thanks to this answer for pointing me in the right direction.
Zdeslav Vojkovic's answer is outdated now.
The JSON.encode() method in dart:convert has an optional toEncodable method that is invoked for objects that are not natively serializable to JSON. It's then up to the user to provide a closure that returns an appropriate serialization of the DateTime.
IMO, it's a flaw in dart:json library that stringify doesn't support additional callback to serialize types lacking the implementation of toJson. Strangely enough, parse function does support reviver argument to customize the deserialization process. Probably the reason was along the lines that user can add toJson for their on types, and core library will take care of 'native' types, but DateTime was omitted (maybe because date serialization in JSON is not really a straightforward story).
Maybe the goal of the team was to use custom types instead of Maps (which is nice), but the drawback here is that if your custom type contains 10 other properties and 1 which is of DateTime type, you need to implement toJson which serializes all of them, even integers and strings. Hopefully, once when Mirror API is ready, there will be libraries that implement serialization 'out-of-band' by reading the reflected info on type, see lower for an example. The promise of Dart is that I would be able to use my types both on server and client, but if I have to manually implement serialization/deserialization for all my models then it is too awkward to be usable.
it also doesn't help that DateTime itself is not very flexible with formatting, as there are no other methods besides toString which returns the format useless for serialization.
So here are some options:
wrap (or derive from) DateTime in your own type which provides toJson
patch json.stringifyJsonValue and submit the change or at least submit an issue :)
use some 3-rd party library like json-object (just an example, it also doesn't support DateTime serialization, AFAIK
I am not really happy with any of them :)
I've added a new package to Dart pub.dev that allows json serialization of objects within a structure. This package Jsonize serialize and deserialize custom classes, and handles DateTime out of the box:
List<dynamic> myList = [1, "Hello!", DateTime.now()];
var jsonRep = Jsonize.toJson(myList);
var myDeserializedList = Jsonize.fromJson(jsonRep);
So will do with your example
var obj = { "dt": new DateTime.now() };
var s = Jsonize.toJson(obj);
var obj2 = Jsonize.fromJson(s);
but can do also this
var obj = DateTime.now();
var s = Jsonize.toJson(obj);
var dt = Jsonize.fromJson(s);