read and write JSON formatted text

These services can be useful to communicate with a remote server or client, save data locally or even debug and understand the structure of a Nit object. There is a single API to write JSON, and three API to read depending on the use case.

Write JSON

Writing Nit objects to JSON format can be useful to communicate with a remote service, save data locally or even debug and understand the structure of an object. There is two related services to write JSON object, the method serialize_to_json and the object JsonSerializer. The method serialize_to_json is actually a shortcut to JsonSerializer, both share the same features.

Write plain JSON

Passing the argument plain=true to serialize_to_json generates plain and clean JSON. This format is non-Nit program, it cannot be fully deserialized back to Nit objects. The argument pretty=true generates JSON for humans, with more spaces and line breaks.

The Nit objects to write must subclass Serializable and implement its services. Most classes from the core library are already supported, including collections, numeric values, etc. For your local objects, you can annotate them with serialize to automate subclassing Serializable and the implementation of its services.

Example

class Person
    serialize

    var name: String
    var year_of_birth: Int
    var next_of_kin: nullable Person
end

var bob = new Person("Bob", 1986)
assert bob.serialize_to_json(pretty=true, plain=true) == """
{
    "name": "Bob",
    "year_of_birth": 1986,
    "next_of_kin": null
}"""

var alice = new Person("Alice", 1978, bob)
assert alice.serialize_to_json(pretty=true, plain=true) == """
{
    "name": "Alice",
    "year_of_birth": 1978,
    "next_of_kin": {
        "name": "Bob",
        "year_of_birth": 1986,
        "next_of_kin": null
    }
}"""

# You can also build JSON objects as a `Map`
var charlie = new Map[String, nullable Serializable]
charlie["name"] = "Charlie"
charlie["year_of_birth"] = 1968
charlie["next_of_kin"] = alice
assert charlie.serialize_to_json(pretty=true, plain=true) == """
{
    "name": "Charlie",
    "year_of_birth": 1968,
    "next_of_kin": {
        "name": "Alice",
        "year_of_birth": 1978,
        "next_of_kin": {
            "name": "Bob",
            "year_of_birth": 1986,
            "next_of_kin": null
        }
    }
}"""

Write JSON with metadata

By default, serialize_to_json and JsonSerializer include metadate in the generated JSON. This metadata is used by JsonDeserializer when reading the JSON code to recreate the Nit object with the exact original type. The metadata allows to avoid repeating an object and its resolves cycles in the serialized objects.

For more information on Nit serialization, see: ../serialization/README.md

Read JSON

There are a total of 3 API to read JSON:

  • JsonDeserializer reads JSON to recreate complex Nit objects (discussed here),
  • the module dynamic provides an easy API to explore JSON objects,
  • the module static offers a low-level service to parse JSON and create basic Nit objects.

The class JsonDeserializer reads JSON code to recreate objects. It can use the metadata in the JSON code, to recreate precise Nit objects. Otherwise, JSON objects are recreated to simple Nit types: Map, Array, etc. Errors are reported to the attribute JsonDeserializer::errors.

The type to recreate is either declared or inferred:

  1. The JSON object defines a __class key with the name of the Nit class as value. This attribute is generated by the JsonSerializer with other metadata, it can also be specified by other external tools.
  2. A refinement of JsonDeserializer::class_name_heuristic identifies the Nit class.
  3. If all else fails, JsonDeserializer uses the static type of the attribute, or the type name passed to deserialize.

The method deserialize_json is a shortcut to JsonDeserializer which prints errors to the console. It is fit only for small scripts and other quick and dirty usage.

Example

class Triangle
    serialize

    var corners = new Array[Point]
    redef var to_s is serialize_as("name")
end

class Point
    serialize

    var x: Int
    var y: Int
end

# Metadata on each JSON object tells the deserializer what is its Nit type,
# and it supports special types such as generic collections.
var json_with_metadata = """{
    "__class": "Triangle",
    "corners": {"__class": "Array[Point]",
                "__items": [{"__class": "Point", "x": 0, "y": 0},
                            {"__class": "Point", "x": 3, "y": 0},
                            {"__class": "Point", "x": 2, "y": 2}]},
    "name": "some triangle"
}"""

var deserializer = new JsonDeserializer(json_with_metadata)
var object = deserializer.deserialize
assert deserializer.errors.is_empty
assert object != null

# However most non-Nit services won't add the metadata and instead produce plain JSON.
# Without a "__class", the deserializer relies on `class_name_heuristic` and the static type.
# The type of the root object to deserialize can be specified by an argument passed to `deserialize`.
var plain_json = """{
    "corners": [{"x": 0, "y": 0},
                {"x": 3, "y": 0},
                {"x": 2, "y": 2}],
    "name": "the same triangle"
}"""

deserializer = new JsonDeserializer(plain_json)
object = deserializer.deserialize("Triangle")
assert deserializer.errors.is_empty

Missing attributes and default values

When reading JSON, some attributes expected by Nit classes may be missing. The JSON object may come from an external API using optional attributes or from a previous version of your program without the attributes. When an attribute is not found, the deserialization engine acts in one of three ways:

  1. If the attribute has a default value or if it is annotated by lazy, the engine leave the attribute to the default value. No error is raised.
  2. If the static type of the attribute is nullable, the engine sets the attribute to null. No error is raised.
  3. Otherwise, the engine raises an error and does not set the attribute. The caller must check for errors and must not read from the attribute.
class MyConfig
    serialize

    var width: Int # Must be in JSON or an error is raised
    var height = 4
    var volume_level = 8 is lazy
    var player_name: nullable String
    var tmp_dir: nullable String = "/tmp" is lazy
end

# ---
# JSON object with all expected attributes -> OK
var plain_json = """
{
    "width": 11,
    "height": 22,
    "volume_level": 33,
    "player_name": "Alice",
    "tmp_dir": null
}"""
var deserializer = new JsonDeserializer(plain_json)
var obj = deserializer.deserialize("MyConfig")

assert deserializer.errors.is_empty
assert obj isa MyConfig
assert obj.width == 11
assert obj.height == 22
assert obj.volume_level == 33
assert obj.player_name == "Alice"
assert obj.tmp_dir == null

# ---
# JSON object missing optional attributes -> OK
plain_json = """
{
    "width": 11
}"""
deserializer = new JsonDeserializer(plain_json)
obj = deserializer.deserialize("MyConfig")

assert deserializer.errors.is_empty
assert obj isa MyConfig
assert obj.width == 11
assert obj.height == 4
assert obj.volume_level == 8
assert obj.player_name == null
assert obj.tmp_dir == "/tmp"

# ---
# JSON object missing the mandatory attribute -> Error
plain_json = """
{
    "player_name": "Bob",
}"""
deserializer = new JsonDeserializer(plain_json)
obj = deserializer.deserialize("MyConfig")

# There's an error, `obj` is partial
assert deserializer.errors.length == 1

# Still, we can access valid attributes
assert obj isa MyConfig
assert obj.player_name == "Bob"

All subgroups and modules

module dynamic

json :: dynamic

Dynamic interface to read values from JSON strings
module error

json :: error

Intro JsonParseError which is exposed by all JSON reading APIs
module json

json :: json

Read and write JSON formatted text using the standard serialization services
module serialization_read

json :: serialization_read

Services to read JSON: deserialize_json and JsonDeserializer
module serialization_write

json :: serialization_write

Services to write Nit objects to JSON strings: serialize_to_json and JsonSerializer
module static

json :: static

Static interface to read Nit objects from JSON strings
module store

json :: store

Store and load json data.
package_diagram json\> json parser_base parser_base json\>->parser_base serialization serialization json\>->serialization parser_base->serialization ...serialization ... ...serialization->serialization json\>... ... json\>...->json\>

Ancestors

group codecs

core > codecs

Group module for all codec-related manipulations
group collection

core > collection

This module define several collection classes.
group core

core

Nit common library of core classes and methods
group meta

meta

Simple user-defined meta-level to manipulate types of instances as object.
group poset

poset

Pre order sets and partial order set (ie hierarchies)
group text

core > text

All the classes and methods related to the manipulation of text entities

Parents

group parser_base

parser_base

Simple base for hand-made parsers of all kinds
group serialization

serialization

Abstract serialization services

Children

group app

app

app.nit, a framework for portable applications
group bundle

android > bundle

A mapping class of String to various value types used by the
group github

github

Nit wrapper for Github API
group ios

ios

iOS support for app.nit
group linux

linux

Implementation of app.nit for the Linux platform
group mongodb

mongodb

MongoDB Nit Driver.
group mpi

mpi

Implementation of the Message Passing Interface protocol by wrapping OpenMPI
group msgpack

msgpack

MessagePack, an efficient binary serialization format
group neo4j

neo4j

Neo4j connector through its JSON REST API using curl.
group nitcorn

nitcorn

Lightweight framework for Web applications development
group popcorn

popcorn

Popcorn
group shared_preferences

android > shared_preferences

Services allowing to save and load datas to internal android device

Descendants

group android

android

Android platform support and APIs
group depth

gamnit > depth

gamnit depth, a framework to create portable 3D games in Nit.
group egl

egl

Interface between rendering APIs (OpenGL, OpenGL ES, etc.) and the native windowing system.
group examples

app > examples

group examples

ios > examples

group flat

gamnit > flat

Simple API for 2D games, built around Sprite and App::update
group gamnit

gamnit

Portable game and multimedia framework for Nit
group glesv2

glesv2

OpenGL graphics rendering library for embedded systems, version 2.0
group graph

neo4j > graph

Provides an interface for services on a Neo4j graphs.
group intent

android > intent

Services allowing to launch activities and start/stop services using
group network

gamnit > network

Easy client/server logic for games and simple distributed applications
group notification

android > notification

Services to show notification in the Android status bar
group service

android > service

Android service support for app.nit centered around the class Service
group src

mpi > examples > src

group src

android > examples > src

group src

nitcorn > examples > src

group ui

android > ui

Views and services to use the Android native user interface
group ui

ios > ui

Implementation of app::ui for iOS
group virtual_gamepad

gamnit > virtual_gamepad

Virtual gamepad mapped to keyboard keys for quick and dirty mobile support