jsontree
Statically typed JSON tree.
The tree can contain JSON-compatible atomic types and nothing else. That is,
only String
, int
, double
, bool
or null
— combined by nested Map
or
List
objects.
This allows you to prevent data errors at a very early stage. You will see warnings from IDE and the program will not compile.
Example
Create a tree in declarative style:
import 'package:jsontree/jsontree.dart';
void main() {
final tree = {
"planet": "Mars".jsonNode,
"diameter": 6779.jsonNode,
"satellites": ["Phobos".jsonNode, "Deimos".jsonNode].jsonNode
}.jsonNode;
print(tree.toJsonCode());
// {"planet":"Mars","diameter":6779,"satellites":["Phobos","Deimos"]}
}
Or create the tree in an imperative style:
import 'package:jsontree/jsontree.dart';
void main() {
final satellites = MutableJsonList.empty();
satellites.data.add("Phobos".jsonNode);
satellites.data.add("Deimos".jsonNode);
final tree = MutableJsonMap.empty();
tree.data["planet"] = "Mars".jsonNode;
tree.data["diameter"] = 6779.jsonNode;
tree.data["satellites"] = satellites;
print(tree.toJsonCode());
// {"planet":"Mars","diameter":6779,"satellites":["Phobos","Deimos"]}
}
Motivation
Imagine that we need to create some JSON request
, that will be later converted
to JSON and sent to server.
BAD: dynamic typing
import 'dart:convert';
main() {
final request = <String, dynamic>{}; // to be converted to JSON
// DateTime is not convertible, but we don't know that yet
request["time"] = DateTime.now(); // oops
request["message"] = "Hi!";
// runtime exception: DateTime cannot be converted
send(json.convert(request));
}
GOOD: static typing
import 'dart:convert';
import 'package:jsontree/jsontree.dart';
respond() {
final request = MutableJsonMap(); // no dynamic types
// to place an object inside MutableJsonMap we are forced to convert each
// parameter to a JsonNode. But there's no way to convert DateTime to it,
// so we have to do it right
request["time"] = DateTime.now().millisecondsSinceEpoch.jsonNode;
request["message"] = "Hi!".jsonNode;
// no errors, as it should be
send(json.convert(request));
}
JsonNode tree creation
x.jsonNode
creates an object that wraps the x
value. The type of the
object depends on the type of x
.
For example, 5.jsonNode
creates JsonInt(5)
. And 5.23.jsonNode
creates JsonDouble(5.23)
.
This works for collections as well.
final sheldon = {
'name': 'Sheldon'.jsonNode,
'surname': 'Cooper'.jsonNode,
'iq': 187.jsonNode,
'girlfriends': 1.jsonNode
}.jsonNode;
// you can't add .jsonNode to the map if you miss at least
// one .jsonNode added to elements
final leonard = {
'name': 'Leonard'.jsonNode,
'surname': 'Hofstadter'.jsonNode,
'iq': 173.jsonNode,
'girlfriends': 4.jsonNode
}.jsonNode;
// connect these nodes into an even larger structure
final tree = {
'science': 'physics'.jsonNode,
'neighbours': [leonard, sheldon].jsonNode
}.jsonNode;
Regardless of the type, all the wrapper objects will be inherited from the
base JsonNode
. If you have created a JsonNode
, you can be sure that there is
JSON-compatible data inside.
JsonNode tree to JSON string
For any JsonNode
object, you can call the .toJsonCode()
method to convert it
to JSON string.
import 'package:jsontree/jsontree.dart';
...
final tree = [1.jsonNode, 2.jsonNode].jsonNode;
print(tree.toJsonCode());
You can also pass the tree directly to json.convert
:
import 'package:jsontree/jsontree.dart';
import 'dart:convert';
...
final tree = [1.jsonNode, 2.jsonNode].jsonNode;
print(json.convert(tree));
JSON string to JsonNode tree
Parsing JSON with this library only makes sense if you want to use the parsed values to create another tree.
final a = JsonNode.fromJsonCode(src1);
final b = JsonNode.fromJsonCode(src2);
print([a, b, "something else".jsonNode].jsonNode.toJsonCode())
JsonNode tree to original objects
You can also call JsonNode.unwrap()
to get rid of all the wrappers and get the
original set of Dart objects. Because these objects were validated when the tree
was created, the result is guaranteed to be able to be converted to JSON.
import 'package:jsontree/jsontree.dart';
import 'dart:convert'
...
JsonList tree = [1.jsonNode, 2.jsonNode].jsonNode;
List<int> list = tree.unwrap(); // [1, 2]
// of course, the list convertible to JSON
print(json.convert(dartList));
Objects to JsonNode tree
Such conversion is contrary to the purpose of the library. It requires dynamic type checking and can lead to runtime errors.
But if you already have data structures ready, this might be a reasonable compromise.
final leonard = {
'name': 'Leonard',
'surname': 'Hofstadter',
'iq': 173,
};
JsonNode tree = JsonNode.wrap(leonard);
JsonNodes immutability
By default, all objects are immutable.
JsonMap m = {"a": 1.jsonNode, "b": 2.jsonNode}.jsonNode;
// you can read m or m.data, but cannot change
There are also mutable versions for lists and maps.
var m = MutableJsonMap({"a": 1.jsonNode, "b": 2.jsonNode});
// you can read/write m and m.data
Mutability and immutability are achievable after the creation of objects.
JsonMap readOnly = {"a": 1.jsonNode, "b": 2.jsonNode}.jsonNode;
MutableJsonMap readWrite = readOnly.toMutable(); // creates a copy
readWrite["c"] = 3.jsonNode;
JsonMap readOnlyAgain = readWrite.asImmutable(); // wraps the data as immutable
toMutable
will create a copy of the data, respecting the immutability of
the original objects.
asImmutable
will just wrap the data into an object, that does not allow
modification.
Hierarchy
JsonAny
^^ JsonValue
^^ JsonInt
^^ JsonInt53 (JavaScript range)
^^ JsonInt64 (full int64 range)
^^ JsonDouble
^^ JsonString
^^ JsonList
^^ MutableJsonList
^^ JsonMap
^^ MutableJsonMap
^^ JsonNull
By default, all the objects are immutable except MutableJsonMap
and MutableJsonList
.
Integer ranges
By default, int.jsonNode
creates a JsonInt53
object. It only allows you to
set integer values that will not lose precision in JavaScript.
final a = 5.jsonNode; // no problem
final b = 9999999999999999.jsonNode; // throws ArgumentError
This restriction is relevant because JSON is literally JavaScript Object Notation.
But most languages are able to read larger numbers from JSON. To store the full
range number, use int.jsonNode64
.
final c = 9999999999999999.jsonNode64; // no problem
License
Copyright © 2022 Artёm IG. Released under the MIT License.