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

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);

Related

ServiceStack.Text CSV serialization of IEnumerable<object> ignores custom serialization functions

Firstly, please forgive any rookie mistakes here - I'm not a regular poster I'm afraid.
Now on to the nitty gritty...
I am trying to use ServiceStack.Text to serialize objects to CSV. If I keep it simple, everything works as expected when serializing objects of a known type.
However I want to serialize many objects and I don't know the type at runtime so I am writing a reusable component where all data is treated as a System.Object. We already do this same routine for Json serialization without problems. But CsvSerializer appears to handle objects differently during serialization.
Sample code
public void TestIEnumerableObjectSerialization()
{
var data = GenerateSampleData();
JsConfig<DateTime>.SerializeFn =
time => new DateTime(time.Ticks, DateTimeKind.Utc).ToString("yyyy-MM-dd HH:mm:ss");
var csv = CsvSerializer.SerializeToCsv(data);
Console.WriteLine(csv);
Assert.Equal("DateTime\r\n"
+ "2017-06-14 00:00:00\r\n"
+ "2017-01-31 01:23:45\r\n",
csv);
}
object[] GenerateSampleData()
{
return new object[] {
new POCO
{
DateTime = new DateTime(2017,6,14)
},
new POCO
{
DateTime = new DateTime(2017,1,31, 01, 23, 45)
}
};
}
public class POCO
{
public DateTime DateTime { get; set; }
}
The result of this code is that the custom serialization function is not invoked, and the DateTime is written out using the standard ToString() method.
The cause?
The CsvWriter.Write method is inspecting the type of the records and if the type is Object it is treated as a Dictionary<string, object> and CsvDictionaryWriter generates the output.
In turn, CsvDictionaryWriter uses the ToCsvField() extension method to write each property a record.
The problem is that ToCsvField() converts the value of each property to a string using ToString() meaning no custom serialization is performed.
JsonSerializer uses TypeSerializer.SerializeToString(text) to serialize the properties of an Object using any configured custom serialization functions; but this doesn't happen with CsvSerializer.
A possible solution?
Without complicating CsvSerializer, the ToCsvField() extension method could be updated to use TypeSerializer to handle the serialization to a string. Here is what I've been testing with so far:
public static object ToCsvField(this object text)
{
var textSerialized = TypeSerializer.SerializeToString(text).StripQuotes();
return textSerialized == null || !CsvWriter.HasAnyEscapeChars(textSerialized)
? textSerialized
: string.Concat
(
CsvConfig.ItemDelimiterString,
textSerialized.Replace(CsvConfig.ItemDelimiterString, CsvConfig.EscapedItemDelimiterString),
CsvConfig.ItemDelimiterString
);
}
So far I haven't come across an issue with this change, although someone may prefer not to allocate a new intermediate variable before the return statement.
Hopefully that is enough information, so on to my questions...
Has anyone else experienced this issue?
Am I doing something wrong and should I be serializing Objects a different way?
If this is a suitable fix/implementation of TypeSerializer, what are the chances of this being addressed in an update to ServiceStack.Text? I would raise an issue on GitHub but the ServiceStack.Text repo doesn't let me raise issues.
Thanks in advance.

Use JSON.decode() in dart to instantiate class from json string

By browsing around I did successfully manage to create a class that can "opt in" dat:convert by exposing a Map toJson() method and can be json-ified with JSON.encode(myClass), more or less like the following:
//My dummy class
class MyClass{
String p1;
String p2;
Map toJson{
return {
'p1':this.p1,
'p2':this.p2
}
}
}
//so I can do
String jsonString = JSON.encode(myClass)
However I'd like to do this even the other way around, like:
String jsonString = '{"p1":"value","p2":"value"}'
MyClass instance = JSON.decode(jsonString)
But so far I've failed to find a way.
I know I can build a constructor for my class that initialises it from a map, something like:
String jsonString = '{"p1":"value","p2":"value"}'
MyClass instance = MyClass.fromMap(JSON.decode(jsonString))
However I was looking for a more "symmetric" way using just JSON.encode() and JSON.decode(), is it somehow doable? Am I missing something?
There is no standard way to encode the class in JSON. {"p1":"value","p2":"value"} doesn't contain any information about what class to instantiate. There is also no standard way to create a new class from as string (what library should be used when several contain a class with the same name, ...
As far as I know a reviver can be used for that purpose
reviver(var key, var value) {
// decode manually
}
final jsonDecoder = new JsonDecoder(reviver);
but the reviver would need to have some hardcoded logic how to recognize what JSON should result in what Dart class and how it should instantiate it and initialize the properties from the JSON.

Serializing Joda DateTime using groovy.json

I have this simple pojo class Fueling that i want to serialize to JSON using the built in Groovy JSON Library. However my application halts when trying to serialize.
class Fueling {
int id;
int mileage;
double amount;
double cost;
DateTime dateTime;
String userId;
}
the following tests renders a java.lang.StackOverflowError:
#Test
void parseJoda(){
def fueling = new Fueling(amount: 1.0, cost: 2.3, mileage: 123, dateTime: DateTime.now(DateTimeZone.UTC));
def jsonF = JsonOutput.toJson(fueling);
}
How can i make this serialization work?
EDIT: The JSON data is for persisting and not for display purpuses, so the actual serialization result format is not important just as long as i am able to get it deserialized again
Given you don't care about the format, one simple workaround is to use Maps as your Groovy JSON API input/output and then add in a little code to translate your domain objects to and from Maps.
Serializing
You can use the Map that is returned by getProperties as-is with two modifications: converting the DateTime instance to it's long millisecond representation and removing the class entry (which lead to memory errors for me)
def serialize(){
def fueling = new Fueling(amount: 1.0, cost: 2.3, mileage: 123, dateTime: DateTime.now(DateTimeZone.UTC));
JsonOutput.toJson(
fueling.properties.collectEntries { k, v ->
[(k): k == 'dateTime' ? v.millis : v] // represent DateTime as milliseconds
}.findAll {
it.key != 'class' // remove the unnecessary 'class' property
}
)
}
Deserializing
You can pass the Map that the JsonSlurper spits out directly to your Fueling constructor with the slight modification of converting the dateTime entry from long back to a DateTime instance
def deserialize() {
String json = '{"userId":null,"cost":2.3,"id":0,"dateTime":1439839235603,"amount":1.0,"mileage":123}'
Map props = new JsonSlurper().parseText(json)
new Fueling(props.collectEntries { k, v ->
[(k): k == 'dateTime' ? new DateTime(v) : v ] // convert back to DateTime
})
}
Of course, there comes a point when your domain object tree is large/complex enough to warrant the use of a more extensible 3rd-party JSON library.

Add JSON serializer to every model class?

When it comes to JSON encoding in Dart, per Seth Ladd's accouncement the finally approved now official way to go is dart:convert + JSON.Encode.
Let's say we have a bunch of model classes (PODOs) such as:
class Customer
{
int Id;
String Name;
}
Now, I'd love to be able to just JSON-encode my domain objects like this:
var customer = new Customer()
..Id = 17
..Name = "John";
var json = JSON.encode(customer);
Unfortunately, this won't work...
Uncaught Error: Converting object to an encodable object failed.
Stack Trace:
#0 _JsonStringifier.stringifyValue (dart:convert/json.dart:416)
#1 _JsonStringifier.stringify (dart:convert/json.dart:336)
#2 JsonEncoder.convert (dart:convert/json.dart:177)
....
... unless we explicitly tell dart:convert how to encode:
class Customer
{
int Id;
String Name;
Map toJson() {
Map map = new Map();
map["Id"] = Id;
map["Name"] = Name;
return map;
}
}
Do I really have to add a toJson method to every single one of my model classes, or is there a better way?
EDIT: this is the simple serialization I'm looking for:
{
"Id": 17,
"Name": "John"
}
Compare to ToJson in ServiceStack.Text, for instance.
Dart's serialization library (see Matt B's answer below) seems like a step in the right direction. However, this ...
var serialization = new Serialization()
..addRuleFor(Customer);
var json = JSON.encode(serialization.write(customer, format: new SimpleJsonFormat()));
... produces just an array with the values (no keys):
[17,"John"]
Using the default SimpleMapFormat on the other hand generates this complex representation.
Still haven't found what I'm looking for...
EDIT 2: Adding some context: I'm building a RESTful web service in Dart, and I'm looking for a JSON serialization which can easily be consumed by any client, not just another Dart client. For instance, querying the Stack Exchange API for this very question will create this JSON response. This is the serialization format I'm looking for. - Or, look at typical JSON responses returned by the Twitter REST API or the Facebook Graph API.
EDIT 3: I wrote a small blog post about this. See also the discussion on Hacker News.
IMO this is a major short-coming in Dart, surprising given its Web Application focus. I would've thought that having JSON support in the standard libraries would've meant that serializing classes to and from JSON would work like water, unfortunately the JSON support seems incomplete, where it appears the choices are to work with loosely typed maps or suffer through un-necessary boilerplate to configure your standard (PODO) classes to serialize as expected.
Without Reflection and Mirrors support
As popular Dart platforms like Flutter doesn't support Reflection/Mirrors your only option is to use a code-gen solution. The approach we've taken in ServiceStack's native support for Dart and Flutter lets you generate typed Dart models for all your ServiceStack Services from a remote URL, e.g:
$ npm install -g #servicestack/cli
$ dart-ref https://techstacks.io
Supported in .NET Core and any of .NET's popular hosting options.
The example above generates a Typed API for the .NET TechStacks project using the generated DTOs from techstacks.io/types/dart endpoint. This generates models following Dart's JsonCodec pattern where you can customize serialization for your Dart models by providing a fromJson named constructor and a toJson() instance method, here's an example of one of the generated DTOs:
class UserInfo implements IConvertible
{
String userName;
String avatarUrl;
int stacksCount;
UserInfo({this.userName,this.avatarUrl,this.stacksCount});
UserInfo.fromJson(Map<String, dynamic> json) { fromMap(json); }
fromMap(Map<String, dynamic> json) {
userName = json['userName'];
avatarUrl = json['avatarUrl'];
stacksCount = json['stacksCount'];
return this;
}
Map<String, dynamic> toJson() => {
'userName': userName,
'avatarUrl': avatarUrl,
'stacksCount': stacksCount
};
TypeContext context = _ctx;
}
With this model you can use Dart's built-in json:convert APIs to serialize and deserialize your model to JSON, e.g:
//Serialization
var dto = new UserInfo(userName:"foo",avatarUrl:profileUrl,stacksCount:10);
String jsonString = json.encode(dto);
//Deserialization
Map<String,dynamic> jsonObj = json.decode(jsonString);
var fromJson = new UserInfo.fromJson(jsonObj);
The benefit of this approach is that it works in all Dart platforms, including Flutter and AngularDart or Dart Web Apps with and without Dart 2’s Strong Mode.
The generated DTOs can also be used with servicestack's Dart package to enable an end to end typed solution which takes care JSON serialization into and out of your typed DTOs, e.g:
var client = new JsonServiceClient("https://www.techstacks.io");
var response = await client.get(new GetUserInfo(userName:"mythz"));
For more info see docs for ServiceStack's native Dart support.
Dart with Mirrors
If you're using Dart in a platform where Mirrors support is available I've found using a Mixin requires the least effort, e.g:
import 'dart:convert';
import 'dart:mirrors';
abstract class Serializable {
Map toJson() {
Map map = new Map();
InstanceMirror im = reflect(this);
ClassMirror cm = im.type;
var decls = cm.declarations.values.where((dm) => dm is VariableMirror);
decls.forEach((dm) {
var key = MirrorSystem.getName(dm.simpleName);
var val = im.getField(dm.simpleName).reflectee;
map[key] = val;
});
return map;
}
}
Which you can mixin with your PODO classes with:
class Customer extends Object with Serializable
{
int Id;
String Name;
}
Which you can now use with JSON.encode:
var c = new Customer()..Id = 1..Name = "Foo";
print(JSON.encode(c));
Result:
{"Id":1,"Name":"Foo"}
Note: see caveats with using Mirrors
I wrote the Exportable library to solve such things like converting to Map or JSON. Using it, the model declaration looks like:
import 'package:exportable/exportable.dart';
class Customer extends Object with Exportable {
#export int id;
#export String name;
}
And if you want to convert to JSON, you may:
String jsonString = customer.toJson();
Also, it's easy to initialize new object from a JSON string:
Customer customer = new Customer()..initFromJson(jsonString);
Or alternatively:
Customer customer = new Exportable(Customer, jsonString);
Please, see the README for more information.
An alternative is to use the Serialization package and add rules for your classes. The most basic form uses reflection to get the properties automatically.
Redstone mapper is the best serialization library I've used. JsonObject and Exportable have the downside that you have to extend some of their classes. With Redstone Mapper you can have structures like this
class News
{
#Field() String title;
#Field() String text;
#Field() List<FileDb> images;
#Field() String link;
}
It works with getters and setters, you can hide information by not annotating it with #Field(), you can rename field from/to json, have nested objects, it works on the server and client. It also integrates with the Redstone Server framework, where it has helpers to encode/decode to MongoDB.
The only other framework I've seen thats on the right direction is Dartson, but it still lack some features compared to Redstone Mapper.
I have solved with:
class Customer extends JsonObject
{
int Id;
String Name;
Address Addr;
}
class Address extends JsonObject{
String city;
String State;
String Street;
}
But my goal is bind data from/to json from/to model classes; This solution work if you can modify model classes, in a contrast you must use solution "external" to convert model classes;
see also: Parsing JSON list with JsonObject library in Dart
Another package solving this problem is built_value:
https://github.com/google/built_value.dart
With built_value your model classes look like this:
abstract class Account implements Built<Account, AccountBuilder> {
static Serializer<Account> get serializer => _$accountSerializer;
int get id;
String get name;
BuiltMap<String, JsonObject> get keyValues;
factory Account([updates(AccountBuilder b)]) = _$Account;
Account._();
}
Note that built_value isn't just about serialization -- it also provides operator==, hashCode, toString, and a builder class.
I have achieve with this:
To make this work, pass explicitToJson: true in the #JsonSerializable() annotation over the class declaration. The User class now looks as follows:
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
#JsonSerializable(explicitToJson: true)
class User {
String firstName;
Address address;
User(this.firstName, this.address);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
You can check here: https://flutter.dev/docs/development/data-and-backend/json#generating-code-for-nested-classes
I prefer using https://ashamp.github.io/jsonToDartModel/ online tool write by myself.
It has features below:
online use, without plugin
support multidimensional list
support complex json
support convert all props to String type
empty props warning
single file
dart keyword protected
instant convert
I think it's better than other tools.Welcome if you have any suggestion, issue or bug report.
Some of the answers are no longer applicable to Flutter 2; here is the process for automatically creating toJson and fromJson methods:
https://flutter.dev/docs/development/data-and-backend/json#creating-model-classes-the-json_serializable-way
PS: I wish this would be as simple as using Newtonsoft library in Asp.Net, this solution is closest to an automated solution

Can I automatically serialize a Dart object to send over a Web Socket?

I just saw that there are some libraries for running a Dart web server, like Start.
So I was thinking something like this..
If both client and server code is written in Dart, is it possible to send "Dart objects" via websockets (or normal REST for that matter) so that the type information remains on the other end? Or do I need to serialize/deserialize via JSON or something similar on the way? Or am I over thinking things here?
regards Oskar
You will need to serialize the Dart object somehow. You can try JSON, or you can try the heavy-duty serialization package.
There is no fully automatic JSON serialization for custom Dart classes. You will need to add a custom toJson serializer and create some sort of fromJson constructor.
e.g. if you had a Person class, you could do something like this:
import 'dart:json' as json;
class Person {
String name;
int age;
Person(this.name, this.age);
Person.fromJson(String json) {
Map data = json.parse(json);
name = data['name'];
age = data['age'];
}
Map toJson() {
return {'name': name, 'age': age};
}
}
Note: the fromJson is just a convention. You will need to call it somehow, there is no built-in mechanism to take an arbitrary JSON string and call the right constructors on your custom object.
As mentioned above, the serialization package is more heavy weight, but much more full featured. Here is an example from its docs:
// uses the serialization package
var address = new Address();
address.street = 'N 34th';
address.city = 'Seattle';
var serialization = new Serialization()
..addRuleFor(address);
Map output = serialization.write(address);
and
// uses serialization
var serialization = new Serialization()
..addRuleFor(address,
constructor: "create",
constructorFields: ["number", "street"],
fields: ["city"]);
You can use the 'exportable' package to render your class to JSON or a map in a more declarative fashion.
import 'package:exportable/exportable.dart';
class Product extends Object with Exportable
{
#export String ProductName;
#export num UnitPrice;
#export bool Discontinued;
#export num UnitsInStock;
Product(this.ProductName, this.UnitPrice, this.Discontinued, this.UnitsInStock);
}
Product prod = new Product("First", 1.0, false, 3 );
var json = prod.toJson(); // {"ProductName":"First","UnitPrice":1.0,"Discontinued":false,"UnitsInStock":3}