Working with JSON

JSON is a format close to GreyCat objects and arrays. It is therefore quite easy to manipulate JSON files.

Read

This section presents how to get a JSON representation from String or File, and how to read the contents. To be noted, in contrast with other types of files, the JSON format only partially allows streaming the content (reading byte by byte). Indeed, JSON Objects and Arrays are blocks that need to be loaded at once.
The JsonReader however allows to read, in sequence, several JSON elements that have been serialized in a file, with or without new lines to separate them. We therefore recommend concatenating several small JSON elements in a single file such as the ndjson format for better performances, rather than one huge element, or several small files.

Read File

The JsonReader will provide the tools to read through the JSON file. The read() function will read the content of the file, element by element. That is, if the first element in your JSON file is an array, it will read and return this array. Likewise, if the first element is an object, it will read the object fully and return it.

fn run() {
    var reader = JsonReader::new("data/myData.json");
    while(reader.available() > 0) {
        var parsedArray = reader.read() as Array;
    }
}

Files are automatically closed when leaving the function where they were opened. If files are written then read in the same function, setting the Writer or Reader to null closes and flushes the contents to disk.

If your file contains several elements concatenated, you can use a loop to read the contents until available() returns 0 (i.e. no more bytes to read).

GreyCat will load the content of JSON with the correct types (int, float, Object, Array, bool, null, String). The result of the read(): any? function can therefore be casted to the expected type, provided that the data in your file conforms to the specifications consistently.
The cast will otherwise throw an exception that you can deal with.

Parse String

In a quite similar way, it is possible to parse a String, obtained for instance as a result of an API HTTP call.

var parsedResult = JsonReader::parse(contentFromAPI as String);

Read arrays

To read a JSON array, you can simply use a for loop as follows. Here again, the arrayElement will have a type any, but you can safely cast to int if you expect an array of integers, etc.

for(position, arrayElement in parsedArray) {
    //Do some with arrayElement
}

Read objects

Since greycat does not have the notion of non typed objects, all json objects will be converted to a Greycat Map that behaves very similar to an Object, because of this you have access to all methods provided by the native Map type

var firstName = jsonObject.get("first_name") as String;

Write

The JsonWriter will help you serialize GreyCat elements into a JSON formatted file.

You first need to create a JsonWriter and give it the path of the file you want to write into as parameter.
Two versions of the JsonWriter are available. One that will override any existing file; the second that will append to the content of an existing file.

fn run() {
    //Overriding writer
    var writer = JsonWriter::new("./myFile.json");

    //Appending writer
    var writer = JsonWriter::new_append("./myFile.json");
}

You can then use write(elem: any?) or writeln(elem: any?) to write a GreyCat element into the file as JSON format. You can write Arrays, Objects, and primitive types.

fn run() {
    [...]
    //Without new line
    writer.write({name: "John", age: 42});

    //With new line
    writer.writeln([true, false, null]);
}

In case you write Greycat defined types to json the output will look slightly different

type Animal {
    age: int;
}

fn run() {
    var animal = Animal {
        age: 10;
    }

    writer.write(animal); //Output   { "_type": "Foo", "age":10  }
}

As you can see there is a new attribute called _type , this allows greycat when you later read the json to convert the object to the correct type and avoid having to deal with a Map.

Beware!!. This only works as long as you have not modified the underlying type, if you were to remove the age attribute or add a new non nullable attribute the parser would fail.

You can still import the json but you will need adjust the reader slightly. This will convert every object into a Map again

fn run() {
    //When reading a file
    var reader = JsonReader::new("data/myData.json").ignore_types();

    //When reading a json from an api or other
    var data = JsonReader::parse_untyped(jsonData as String);
}