- Created by Jacques Marais, last modified on Oct 12, 2018
You are viewing an old version of this page. View the current version.
Compare with Current View Page History
« Previous Version 42 Next »
Description
Helium provides two native JSON types, namely json
and jsonarray.
These types allow developers to represent data in a manner that can, under certain circumstances, be more flexible than only making use of persistent or non-persistent objects. This document details how values for these types can be set and manipulated as well as typical use cases where they might be useful.
Creating and Manipulating JSON
Creating json and jsonarray values from strings
The simplest way to populate a json
or jsonarray
variable is to make use of implicit casting from string
values to the json
and jsonarray
types. With the multiline string declaration this can be achieved as follows:
json jsonPerson = /% { "name": "Jack", "surname": "Marques", "phoneNumber": "555-6162" } %/;
jsonarray jsonPeople = /% [ { "name": "Jack", "surname": "Marques", "phoneNumber": "555-6162" }, { "name": "Bevin", "surname": "Sharp", "phoneNumber": "555-0123" } ] %/;
Note, that no compile time validation is done to validate that the values being converted represents correctly formatted JSON. This means that if any such conversion issues were to arise, it will only be at runtime. Any conversion errors will result in an error message being logged to the Helium Logging Service:
< {"id":"ca7963c5-4d5d-44e9-ba65-dcbb68127b9f","key":"Runtime Error","value":"[PersonResourceV2:23] {\n \"name\": \"Jack\",\n \"surname\": \"Marques\",\n \"phoneNumber\": \"555-6162\n } could not be converted to a com.mezzanine.program.web.core.type.JsonType@577db227 value","millis":1536562229362,"appId":"a7c76024-b379-49e4-a3c0-bd2ef30d28ab","appUserId":null}
In addition to the above conversion error, the result of the conversion will be null
. This might lead to unexpected null pointer exceptions if not handled appropriately in DSL apps.
Although casting from string
values to json
and jsonarray
might be quick and useful in some specific cases, its uses might be limited and, if the values are constructed "by hand", there is a high probability of undesirable runtime errors.
A better approach for constructing JSON values in a DSL app, is to use the supplied json
manipulation built-in functions namely jsonPut
and jsonGet
.
Manipulating json values using jsonPut and jsonGet
In order to use jsonPut
and jsonGet
a json
variable needs to be declared and instantiated. If the variable is not instantiated, any subsequent operations might lead to null pointer exceptions. For instantiation, the implicit casting between string
and json
can again be utilised:
json jsonPersonObject = "{}";
jsonPut
can be used to add fields and values to a json
variable:
jsonPersonObject.jsonPut("name", "John"); jsonPersonObject.jsonPut("surname", "Smith"); jsonPersonObject.jsonPut("luckyNumber", 13); jsonPersonObject.jsonPut("dob", Date:fromString("1936-05-02")); jsonPersonObject.jsonPut("age", (Date:daysBetween(Date:fromString("1936-05-02"), Mez:now()))/365); jsonPersonObject.jsonPut("gender", GENDER.Male); jsonPersonObject.jsonPut("height", 1.83); jsonPersonObject.jsonPut("militaryService", true);
The arguments required for jsonPut
is firstly the name of the json
field, followed by the value for that field.
json
values can also be nested:
// JSON representing contact details json jsonContactDetails = "{}"; jsonContactDetails.jsonPut("phoneNumber", "555-6162"); jsonContactDetails.jsonPut("emailAddress", "john.smith@gmail.com"); // Adding contact details JSON to a JSON person jsonPersonObject.jsonPut("contactDetails", jsonContactDetails);
Similarly, jsonGet
can be used to retrieve values:
string name = jsonPersonObject.jsonGet("name"); string surname = jsonPersonObject.jsonGet("surname"); string phoneNumber = jsonPersonObject.jsonGet("phoneNumber"); int luckyNumber = jsonPersonObject.jsonGet("luckyNumber"); date dob = jsonPersonObject.jsonGet("dob"); int age = jsonPersonObject.jsonGet("age"); GENDER gender = jsonPersonObject.jsonGet("gender"); decimal height = jsonPersonObject.jsonGet("height"); bool milService = jsonPersonObject.jsonGet("militaryService"); json contactDetails = jsonPersonObject.jsonGet("contactDetails");
In the code segment above, note the implicit casting for the values being retrieved.
To reference a value that is nested more that one level deep, for example a person's contact detail phone number from the example above, one needs to make use of multiple jsonGet
statements and intermediate variables. Chaining references to jsonGet
is not valid syntax in the Helium DSL.
// First we get the contact details json object and assign it to an intermediate variable json contactDetails = jsonPersonObject.jsonGet("contactDetails"); // Now we can get specific field values for the contact details string phoneNum = contactDetails.jsonGet("phoneNumber"); string emailAddr = contactDetails.jsonGet("emailAddress");
Manipulating jsonarray values
In the above example we added a single contact detail object to a person where each field represents a channel of communication. Consider an example where we want to add multiple contact details for a person. For example, a collection of emergency contacts.
At present, no built-in functions are provided to help with the construction of jsonarray
variables. Implicit casting between any primitive array and jsonarray
and between arrays of json
and jsonarray
is, however, provided:
// Create parent as emergency contact json parentEmergencyContact = "{}"; parentEmergencyContact.jsonPut("description", "parent"); parentEmergencyContact.jsonPut("mobileNumber", "27763300000"); // Create spouse as emergency contact json spouseEmergencyContact = "{}"; spouseEmergencyContact.jsonPut("description", "spouse"); spouseEmergencyContact.jsonPut("mobileNumber", "27763300001"); // Create a json[] of the above json[] emergencyContacts; emergencyContacts.append(parentEmergencyContact); emergencyContacts.append(spouseEmergencyContact); // Cast it to jsonarray so we can add it to the json object jsonarray emergencyContactsArray = emergencyContacts; // Add it to the person object jsonPersonObject.jsonPut("emergencyContacts", emergencyContactsArray);
To retrieve the individual values one can make use of jsonGet
and implicit casting to json[]
:
// Retrieve the emergency contacts jsonarray retrievedEmergencyContacts = jsonPersonObject.jsonGet("emergencyContacts"); // Cast back to json[] json[] convertedEmergencyContacts = retrievedEmergencyContacts; // Iterate over or reference as normal DSL collection foreach(json currentEmergencyContact: convertedEmergencyContacts) { . . . } json firstEmergencyContact = convertedEmergencyContacts.get(0);
Note that casting from jsonarray
to json[]
is a relatively expensive operation and if possible, it should be avoided for large arrays.
The example below describes the same concepts as discussed above.
json pet1 = /% { "name": "Jasmine", "type": "Dog" } %/; json pet2 = /% { "name": "Markus", "type": "Mole-rat" } %/; json[] petsArray; petsArray.append(pet1); petsArray.append(pet2); // json[] converted to jsonarray jsonarray petsJsonArray = petsArray; // jsonarray converted json[] json[] convertedPetsArray = petsJsonArray;
In the above examples the implicit casting between json[]
and jsonarray
is shown. Casting to primitive arrays is also supported:
int[] numbersPrimitiveArray; numbersPrimitiveArray.append(1); numbersPrimitiveArray.append(2); numbersPrimitiveArray.append(3); // int[] converted to jsonarray jsonarray numbersJsonArray = numbersPrimitiveArray; // jsonarray converted back to int[] int[] numbersPrimitiveArray2 = numbersJsonArray;
// string representing a number array in json format converted to jsonarray jsonarray primeNumbersJsonArray = /%[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]%/; // jsonarray converted to int[] int[] primeNumbersPrimitiveArray = primeNumbersJsonArray;
Reading json and jsonarray values from the app schema
json
and jsonarray
types can also be used for object attributes. These values are represented in the app schema by the PostgresSQL jsonb type and can be retrieved in any way that other attribute values can.
No compile time validation is done when converting string
values to json
or jsonarray
. This means that if any incorrectly formatted JSON is used, it will result in a runtime error. These errors will be logged and can be inspected using the Helium Logging Service.
Helium does not support chaining of the jsonGet
built-in function. To get values for a json
field that is more than one level deep in a top level json
object, multiple jsonGet
statements and intermediate variables need to be used.
JSON Values as Object Attributes
Lets consider a different example where a Person
object is created to represent most of the attributes of a person while including a single json
attribute to represent the contact details of a person:
@NotTracked persistent object Person { string name; string surname; date dob; json contactDetails; }
Currently integration between Helium and Journey does not support JSON types. Note the inclusion of the NotTracked annotation above to avoid any possible syncing for this object. This is compulsory for any persistent object that contains a json
or jsonarray
attribute. If the NotTracked annotation is not included for persistent objects containing json
or jsonarray
attributes a compiler error will generated:
Loading source files... Compiling the application... |--------------------------------------------------------------------------------------------------------------------------------------------| | The module "Web" generated the following errors | |--------------------------------------------------------------------------------------------------------------------------------------------| | # | Message | |--------------------------------------------------------------------------------------------------------------------------------------------| | 1 | [Person:2] The custom object Person is persistent, thus needs to be marked with @NotTracked if json attribute types are declared. | |--------------------------------------------------------------------------------------------------------------------------------------------|
Inspecting the database table created by Helium for the Person object, reveals the contactdetails column represented by the jsonb type in PostgreSQL:
helium-app-1=# \d person Table "snapshot_1535373223392_001.person" Column | Type | Modifiers ----------------+-----------------------------+-------------------------------------------------------------- _id_ | uuid | not null _tstamp_ | timestamp without time zone | not null person | text | _tx_id_ | bigint | not null default txid_current() _change_type_ | __he_obj_change_type__ | not null default 'create'::__he_obj_change_type__ _change_seq_ | bigint | not null default nextval('__he_sync_change_seq__'::regclass) name | text | surname | text | dob | date | contactdetails | jsonb | Indexes: "person_pkey" PRIMARY KEY, btree (_id_) "__idx_he_sync_person_txid__" btree (_tx_id_)
Any method of populating attributes in general is also applicable to JSON attributes. This includes the use of database functions.
Helium Journey integration does not currently support objects with json
or jsonarray
attributes. For this reason, any persistent object with json
or jsonarray
attributes needs to be annotated with NotTracked. Failure to do so will result in a compiler error.
Using Native JSON with an Inbound API
The native JSON types json
and jsonarray,
as discussed in this document, are supported as valid return types for any inbound API function. They are also supported as valid parameters for put and post API functions. More details regarding the inbound API annotations and supported parameter and return types for API functions can be found here.
- No labels
Add Comment