/
Native JSON Types

Native JSON Types


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 json and jsonarray values from strings

json and jsonarray variables can be populated by casting from strings. See, for example, how this can be done for string literals using multiline string declaration 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. In addition, if the values are constructed "by hand", there is a high probability of undesirable runtime errors.

Another approach for constructing JSON values in a DSL app, is to make use of objects and the existing built-in functions namely jsonGet, jsonPut, jsonRemove, jsonKeys, jsonContains.

See the sections below for details.




Creating json and jsonarray values from objects

json and jsonarray variables can be populated by casting from objects and object collections respectively.

Consider the following model object:

object Person { 
	string name; 
	string surname; 
	date dob; 
	json contactDetails; 
}

Casting to json can be done implicitly as follows:

Person person = getPersonFromDb(...);
json personJson = person;

The above will create the following example json:

{
	"_id": "b9bf8965-087f-47a1-8b9b-ab77a01a494d"
	"name": "John",
	"surname": "Doe",
	"dob": 1738846993000,
	"contactDetails": {
		"phoneNumber": "555-6162",
		"emailAddress": "john.doe@gmail.com"
	}
}

Note that the object id is included under the property with name _id.

Relationships (many to one or one to one) will be included and the value or the related object's internal id will be used.

Similarly a jsonarray value can be created by using an object collection:

Person[] people = getPeopleFromDb();
jsonarray peopleJson = people;




Built-in methods for manipulating json

In order to use any of the built-in methods for json types, 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 of an empty json object, the implicit casting between string and json can be used as follows:

json jsonPersonObject = "{}";


jsonPut

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 property, followed by the value for that property.

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);


jsonGet

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");

jsonContains

jsonContains can be used to determine whether a specific property is already present in the json variable.

// JSON representing contact details 
json jsonContactDetails = "{}"; 
jsonContactDetails.jsonPut("phoneNumber", "555-6162");
jsonContactDetails.jsonPut("emailAddress", "john.smith@gmail.com");

// This will evaluate to true since the property is present
bool containsPhoneNumber = jsonContactDetails.jsonContains("phoneNumber");

// This will evaluate to false since the property is not present
bool containsNickname = jsonContactDetails.jsonContains("nickname");


jsonRemove

To remove an individual property from a json variable, the jsonRemove built-in function can be used: 

// JSON representing contact details 
json jsonContactDetails = "{}"; 
jsonContactDetails.jsonPut("phoneNumber", "555-6162");
jsonContactDetails.jsonPut("emailAddress", "john.smith@gmail.com");

// This will evaluate to true since the property is present
bool containsPhoneNumber = jsonContactDetails.jsonContains("phoneNumber");

// This will remove the mobile number property
jsonContactDetails.jsonRemove("phoneNumber");

// This will now evaluate to false since the property has been removed
containsPhoneNumber = jsonContactDetails.jsonContains("phoneNumber");



jsonKeys

To get a collection of json property names for a json variable, the jsonKeys built-in function can be used:

// JSON representing contact details
json jsonContactDetails = "{}";
jsonContactDetails.jsonPut("phoneNumber", "555-6162");
jsonContactDetails.jsonPut("emailAddress", "john.smith@gmail.com");

// This will return a collection of strings representing the property names
// ["phoneNumber", "emailAddress"]
string[] keys = jsonContactDetails.jsonKeys();

// Keys can be used to iterate over all the properties 
foreach(string key: keys) {
	string value = jsonContactDetails.jsonGet(key);
}





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.


















Manipulating jsonarray values

At present, no built-in functions are provided to help with the construction of jsonarray variables. Implicit casting between any primitive or object collection and jsonarray and between collections 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;




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. Both json and jsonarray types will be represented on PostgreSQL as jsonb.

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 objects with json or jsonarray attributes need 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.





Related content

Data Types
More like this
Inbound API
More like this
1.63.0 Release Notes
1.63.0 Release Notes
Read with this
Apps API | V1
More like this
Quick Reference
Read with this
Lesson 27: Expanding Inbound REST API Functionality
Lesson 27: Expanding Inbound REST API Functionality
More like this