Is it really necessary to introduce JSON in 2026? Any developers had to deal with JSON at least one time in his career. Anyway, JSON (JavaScript Object Notation) is an open standard format designed for web development usage. In fine, it became one of the most used data format.
Before working on more challengin but interesting serializer like CBOR or Protocol Buffer, let take a moment to learn how use JSON in Dart.
import 'dart:convert';
When one needs to encode or decode JSON, two ways existing, using the JsonCodec() class or use the specific class for encoding (JsonEncoder()) or for decoding (JsonDecoder()). In both case, the result will be the same.
Encoding
JSON encoder is directly integrated in the Dart SDK, and can be included in your code by simply importing dart:convert package.
import 'dart:convert';
Let create a first function called encode() to encode any kind of object using the json.encode() interface.
void encode(Object data, {String label = ""}) {
print("-- ${label}:");
var encoded = json.encode(data);
print(encoded);
print("");
}
Now, let create another function called encode2(). This one will instantiate an object from the JsonEncoder() class.
void encode2(Object data, {String label = ""}) {
var encoder = JsonEncoder();
print("-- ${label}:");
var encoded = encoder.convert(data);
print(encoded);
print("");
}
Great, we have two ways to encode our objects, the main entry-point is still missing though. Let fix that.
void main() {
print("== encode with JsonCodec");
encode(1, label: "integer(1)");
encode("test", label: "string(test)");
encode([], label: "list()");
encode([1,2,3], label: "list(1,2,3)");
encode({}, label: "map()");
encode({"a": 1}, label: "map(a, 1)");
encode(0.001, label: "float(0.001)");
encode(true, label: "bool(true)");
encode(false, label: "bool(false)");
print("encode with JsonEncoder");
encode2(1, label: "integer(1)");
encode2("test", label: "string(test)");
encode2([], label: "list()");
encode2([1,2,3], label: "list(1,2,3)");
encode2({}, label: "map()");
encode2({"a": 1}, label: "map(a, 1)");
encode2(0.001, label: "float(0.001)");
encode2(true, label: "bool(true)");
encode2(false, label: "bool(false)");
}
The time has come to execute this program and see the result.
$ dart run bin/encode.dart
== encode with JsonCodec
-- integer(1):
1
-- string(test):
"test"
-- list():
[]
-- list(1,2,3):
[1,2,3]
-- map():
{}
-- map(a, 1):
{"a":1}
-- float(0.001):
0.001
-- bool(true):
true
-- bool(false):
false
encode with JsonEncoder
-- integer(1):
1
-- string(test):
"test"
-- list():
[]
-- list(1,2,3):
[1,2,3]
-- map():
{}
-- map(a, 1):
{"a":1}
-- float(0.001):
0.001
-- bool(true):
true
-- bool(false):
false
It looks okay, the main Dart types are supported, but what will happen if we give an unsupported type to the encoder?
void main() {
json.encode(Object());
}
The code is simple, we pass a raw Object() to json.encode().
$ dart bin/encode_exception.dart
Uncaught Error, error: Error: Converting object to an encodable object failed: Instance of 'Object'
Doh, it produces an error! In fact, this is normal, the JSON encoder does not know how to deal with such objects. JSON only support a subset of all Dart type, so, in this case, we need to find a way to convert (serialize) similar objects to something compatible with JSON. In most of the case, it will be converted as String(), perhaps using Base64 or something similar.
To illustrate that, let create a new class called Tuple, it will contain two public attributes, a left and a right one. Both are String(). To represent this kind of object in JSON we can easily use a Map containing at least two fields, one for the left and one of the right. Adding a method called toJson() here will help us.
class Tuple {
String left = "";
String right = "";
Tuple(this.left, this.right);
Map<String, String> toJson() => {
"_object": "Tuple",
"left": left,
"right": right
};
}
The JSON encoder needs a way to know how to convert this new object, it can be done by creating a simple function like the following one. The idea is to check the type of the object passed as argument and to return another object compatible with the JSON format.
Function(dynamic) toEncodable() {
return (o) {
switch (o) {
case Tuple(): return o.toJson();
case Object(): return o.toString();
}
};
}
The toEncodable() function can be passed to the JsonCodec constructor via the toEncodable attribute. The rest is pure testing.
void main() {
var encoder = JsonCodec(toEncodable: toEncodable());
var encoded = encoder.encode([
1,
"test",
Object(),
Tuple("Left1", "Right1")
]);
print(encoded);
}
Let execute the code and check the result.
$ dart bin/encode_encable.dart
[1,"test","Instance of 'Object'",{"_object":"Tuple","left":"Left1","right":"Right1"}]
Our object is correctly converted to a JSON map containing a left and right field. Regarding the raw object, we have now a string containing "Instance of 'Object'", that's the direct result of the toString() method.
Last trick, it is also possible to encode a JSON using indentation via the JsonEncoder.withIndent() constructor.
import 'dart:convert';
void main() {
print(JsonEncoder
.withIndent(" ")
.convert(
{"test": [1,2,3]}
)
);
}
Let execute our code, the output should be a JSON with indentation.
$ dart bin/encode_indent.dart
{
"test": [
1,
2,
3
]
}
That's right! It works! Next step, decoding JSON.
Decoding
Decoding a JSON String is similar than encoding Dart objects, one can use JsonCodec or create a JsonDecoder object, the result will be the same.
void main() {
print(
json.decode(
'{"test": 1}'
)
);
print(
JsonDecoder()
.convert(
'{"test": 1}'
)
);
}
Let execute this code.
$ dart run bin/decode.dart
{test: 1}
{test: 1}
Without surprise, it returns a Map(), but, what if we want to return a specific Object instead? Let take our Tuple class back from the Encoding section.
class Tuple {
String left = "";
String right = "";
Tuple(this.left, this.right);
Map<String, String> toJson() => {
"_object": "Tuple",
"left": left,
"right": right
};
}
The JsonDecoder class can accept a reviver() function passed as parameter and can be used for this kind of situation. When some data looks like it's a specific object, the reviver() function will be able to return the correct instantiated object. Here an example:
Object? Function (Object? key, Object? value)? reviver() {
return (key, value) {
print("--> key: $key");
print("--> value: $value");
switch (value) {
case {"_object": "tuple"}:
var v = value as Map<String,dynamic>;
return Tuple(v["left"] ?? "", v["right"] ?? "");
default: return value;
}
};
}
The snippet above will check each value returned by the decoder, and if one of these values are matching a specific kind of pattern (in our case if the value is a Map containing a field object containing the string tuple), then it will create the object. Let check that with a new main entry-point.
void main() {
var toEncode = {
"1": [{
"test": {
"data": 2
}
}]
};
var decoder = JsonDecoder(reviver());
var x = decoder.convert(
json.encode(
toEncode
)
);
print("==> final: $x");
}
Now, we can execute the code and check the output.
$ dart bin/decode_reviver.dart
--> key: data
--> value: 2
--> key: _object
--> value: tuple
--> key: left
--> value: left
--> key: right
--> value: right
--> key: tuple
--> value: {_object: tuple, left: left, right: right}
--> key: test
--> value: {data: 2, tuple: Instance of 'Tuple'}
--> key: 0
--> value: {test: {data: 2, tuple: Instance of 'Tuple'}}
--> key: 1
--> value: [{test: {data: 2, tuple: Instance of 'Tuple'}}]
--> key: null
--> value: {1: [{test: {data: 2, tuple: Instance of 'Tuple'}}]}
==> final: {1: [{test: {data: 2, tuple: Instance of 'Tuple'}}]}
As you can see, the returned value by the decoder includes an instance of a Tuple class. The code is still a bit messy, not enough check, but the idea is there.
Conclusion
This post was a small one, JSON is a simple format, and the interfaces used to encode/decode it are relatively easy to use, at least, for simple use cases. JSON can also be specified using schema or used as serializer, but it will be seen in another publication. As usual, for the ones would like to dive deeper in the subject, here few interesting links for you.
Official JSON Dart Tutorial, where you can see more complex examples;
JsonCodec class, where you will find the complete documentation API for the JsonCodec class;
JsonDecoder class, where you will learn how to use the JsonDecoder class;
JsonEncoder class, where the full description of the JsonEncoder class can be found;
dart:convertJSON source code, where you will find the full documentation of theconvertpackage, containing also the classes for Base64 or UTF8/Unicode;Passing Data from Dart to JSON for Backend Integration by @avwerosuoghene
Mastering JSON Models in Dart & Flutter: From Basics to Complex Models by Iftekhar Ahmed on Medium
How to Parse JSON in Dart/Flutter by Vikranh Salian on Medium
Mastering JSON Encoding and Decoding in Flutter/Dart: A Comprehensive Guide for API Integration by Bhaveshh Sachala on Medium
Have fun!
Top comments (0)