56

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.

Max
  • 9,220
  • 10
  • 51
  • 83
  • 1
    Small note: the JSON converter uses `.toJson` by default, but one can configure the `toEncodable` function. – Florian Loitsch Nov 17 '13 at 11:15
  • Why isn't the output from Serialization what you are looking for? – Seth Ladd Nov 18 '13 at 00:22
  • @SethLadd because it can't easily be consumed by a non-Dart client. See EDIT 2 above. – Max Nov 18 '13 at 01:18
  • 1
    @SethLadd: any plans to either to make it possible to create output from Serialisation as Max wrote or even better, support Json.decode for classes automaticly? If no, would you suggest the solution with the "exportable" Library? – mica Jan 02 '14 at 17:11
  • Unfortunately, there's no universal JSON serialization of objects for all platforms. Whatever Dart picks, someone will want something different. :( That's why the Serialization library gives you so much control: you can output exactly what you want. I have not tried the `exportable` library, but I should. – Seth Ladd Jan 02 '14 at 17:26
  • 1
    @SethLadd: Tnx! Is there an easy way produce a format as Max wrote with a special Formater. Do you have a tip or an example how to write such a Formater – mica Jan 02 '14 at 23:34
  • I am really disappointed that something that trivial and important for allmost all REST based applications is not working well with dart. I have spend hours over hours with solving problems why object A is able to be marshalled by the JSON.encode method and another not. And don't let me get started about the need to create the toMap stuff for each class. This drives me nuts. – Patrick Cornelissen Apr 29 '15 at 05:28
  • Seth's comment on G+ is gone; Google+ is no longer available for consumer (personal) and brand accounts – Chris Nadovich Mar 02 '23 at 16:56

9 Answers9

35

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

mythz
  • 141,670
  • 29
  • 246
  • 390
  • See the serialization library as an example. – Seth Ladd Nov 18 '13 at 00:20
  • 6
    @SethLadd The appeal of JSON is that it's a perfect programatic fit for programming languages. Having to manually configure serialization for each class defeats the purpose, making it tedious and error-prone. There really should be first-class support for JSON with a default behavior that works as expected, i.e. like it does with JavaScript. – mythz Nov 18 '13 at 01:45
  • @mythz your mixin solution works well, thanks! At least on the server side. Unfortunately, this won't work in the browser yet due to incomplete mirrors support in dart2js. Also, DateTime member fields break the serialization because there's no toJson implementation on DateTime- I wish it would just serialize millisecondsSinceEpoch by default. – Max Nov 18 '13 at 02:21
  • 2
    I see this was posted in 2013, is this still the only way to (de)serialize to JSON, or is there a better way to this now (in 2015)? – Jan Vladimir Mostert Jan 02 '15 at 06:53
  • It's now 2023 @JanVladimirMostert and pretty much nothing has changed. Manual transcribing or code generation. Those are your choices. – Chris Nadovich Mar 02 '23 at 16:31
14

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.

Leksat
  • 2,923
  • 1
  • 27
  • 26
7

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.

Matt B
  • 6,192
  • 1
  • 20
  • 27
  • That looks like a step in the right direction. However, I'm still unable to produce the simple serialization I'm looking for. See EDIT. – Max Nov 17 '13 at 15:10
  • 1
    @Max can you be more specific about your requirements? Serialization does automatic serialization/deserialization for nearly any object. – Seth Ladd Nov 18 '13 at 00:22
  • @SethLadd Yes, see EDIT 2 in question. Agreed, serialization works great for serialization/deserialization e.g. to a data store. Doesn't quite generate what I was hoping for though to build a RESTful web service. – Max Nov 18 '13 at 01:23
7

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.

Cristian Garcia
  • 9,630
  • 6
  • 54
  • 75
4

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

Community
  • 1
  • 1
Domenico Monaco
  • 1,236
  • 12
  • 21
4

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.

David Morgan
  • 466
  • 3
  • 6
3

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

0

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
user3044484
  • 463
  • 5
  • 7
0

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

dvdmn
  • 6,456
  • 7
  • 44
  • 52