{#========================================== Docs : "JsonObjects" ==========================================#}

JsonObjects

JsonObject (and JsonArray) are components provided by Spincast to mimic real Json objects. You can think of JsonObjects as Map<String, Object> on steroids!

JsonObjects provide methods to get elements from them in a typed manner They also support JsonPaths, which is an easy way to navigate to a particular element in the JsonObject. JsonObjects are also very easy to validate.

Here's a quick example of using a JsonObject :

// Creates a new JsonObject
JsonObject jsonObj = getJsonManager().create();

// Adds an element
jsonObj.set("myElement", "42");

// Gets the element as a String
String asString = jsonObj.getString("myElement");

// Gets the same element as an Integer
Integer asInteger = jsonObj.getInteger("myElement");

JsonObject supports those types, natively :

Getters are provided for all of those native types :

Every Getter has an overloaded version that you can use to provide a default value in case the requested element if not found (by default, null is returned if the element is not found). Let's see an example :

// Creates an empty JsonObject
JsonObject jsonObj = getJsonManager().create();

// Tries to get an inexistent element...
// "myElement" will be NULL
String myElement = jsonObj.getString("nope");

// Tries to get an inexistent element, but also specifies a default value...
// "myElement" will be "myDefaultValue"!
String myElement = jsonObj.getString("nope", "myDefaultValue");

When you add an object of a type that is not managed natively, the object is automatically converted to a JsonObject (or to a JsonArray, if the source is an array or a Collection). Spincast does this by using the SpincastJsonManager#convertToNativeType(...) method, which is based on Jackson by default. For example :

// Gets a typed user
User user = getUser(123);

// Adds this user to a JsonObject
JsonObject jsonObj = getJsonManager().create();
jsonObj.set("myUser", user);

// Gets back the user... It is now a JsonObject!
JsonObject userAsJsonObj = jsonObj.getJsonObject("myUser");

Note that you can have control over how an object is converted to a JsonObject by implementing the ToJsonObjectConvertible interface. This interface contains a convertToJsonObject() method that you implement to convert your object to a JsonObject the way you want. There is a similar ToJsonArrayConvertible interface to control how an object is converted to a JsonArray.

Creating a JsonObject

You can create an JsonObject (or an JsonArray) by using the JsonManager component. This JsonManager can be injected where you want, or it can be accessed through the json() add-on, when you are inside a Route Handler :

public void myHandler(AppRequestContext context) {

    // JsonObject creation
    JsonObject obj = context.json().create();
    obj.set("name", "Stromgol");
    obj.set("lastName", "Laroche");

    // JsonArray creation
    JsonArray array = context.json().createArray();
    array.add(111);
    array.add(222);

    // Or, using the JsonManager directly (if
    // injected in the current class) :
    JsonObject obj2 = getJsonManager().create();

    //...
}

Cloning and Immutability

By default, any JsonObject (or JsonArray) added to another JsonObject is added as is, without being cloned. This means that any external modification to the added element will affect the element inside the JsonObject, and vice-versa, since they both refere to the same instance. This allows you to do something like :

JsonArray colors = getJsonManager().createArray();
JsonObject obj = getJsonManager().create();

// Adds the array to the obj
obj.set("colors", colors);

// Only then do we add elements to the array
colors.add("red");
colors.add("blue");

// This returns "red" : the array inside the JsonObject
// is the same instance as the external one.
String firstColor = obj.getArrayFirstString("colors");

Sometimes this behavior is not wanted, though. You may need the external object and the added object to be two distinct instances so modifications to one don't affect the other! In those cases, you can call the "clone()" method on the original JsonObject object, or you can use "true" as the "clone" parameter when calling set(...)/add(...) methods. Both methods result in the original object being cloned. Let's see an example :

JsonArray colors = getJsonManager().createArray();
JsonObject obj = getJsonManager().create();

// Add a *clone* of the array to the object
obj.set("colors", colors, true);

// Or :
obj.set("colors", colors.clone());

// Then we add elements to the original array
colors.add("red");
colors.add("blue");

// This will now return NULL since a *new* instance of the 
// array has been added to the JsonObject!
String firstColor = obj.getArrayFirstString("colors");

Note that when you clone a JsonObject, a deep copy of the original object is made, which means the root object and all the children are cloned. The resulting JsonObject is guaranteed to share no element at all with the original object.

We also decided to make JsonObject and JsonArray objects mutable by default. This is a conscious decision to make those objects easy to work with : you can add and remove elements from them at any time.

But if you need more safety, if you work in a complex multi-threaded environment for example, you can get an immutable version of a JsonObject object by calling its .clone(false) method, using false as the "mutable" parameter :

JsonObject obj = getJsonManager().create();
obj.set("name", "Stromgol");

// "false" => make the clone not mutable!
JsonObject immutableClone = obj.clone(false);

// This will throw an exception!
immutableClone.set("nope", "doesn't work");

When you create an immutable clones, the root element and all the children are cloned as immutable. In fact, JsonObject objects are always fully mutable or fully immutable! Because of this, if you try to add an immutable JsonObject to a mutable one, a mutable clone will be created from the immutable object before being added. Same thing if you try to add an mutable JsonObject to an immutable one : an immutable clone will be created from the mutable object before being added.

At runtime, you can validate if a JsonObject is mutable or not using : if(myJsonObj.isMutable()).

JsonObject methods

Have a look at the JsonObject Javadoc for a complete list of available methods. Here we're simply going to introduce some interesting ones, other than set(...), getXXX(...) and clone(...) we already saw :

JsonArray extra methods

Have a look at the JsonArray Javadoc for a complete list of available methods. Here are some interesting ones, other than the ones also available on JsonObjects :

JsonPaths

In Spincast, a JsonPath is a simple way of expressing the location of an element inside a JsonObject (or a JsonArray). For example :


String title = myJsonObj.getString("user.books[3].title");
In this example, "user.books[3].title" is a JsonPath targetting the title of the fourth book from a user element of the myJsonObj object.

Without using a JsonPath, you would need to write something like that to retrieve the same title :

JsonObject user = myJsonObj.getJsonObjectOrEmpty("user");
JsonArray books = user.getJsonArrayOrEmpty("books");
JsonObject book = books.getJsonObjectOrEmpty(3);
String title = book.getString("title");
As you can see, a JSonPath allows you to easily navigate a JsonObject without having to extract any intermediate elements.

Here is the syntax supported by JsonPaths :

You can also use JsonPaths to insert elements at specific positions! For example :

// Creates a book object
JsonObject book = getJsonManager().create();
book.set("title", "The Hitchhiker's Guide to the Galaxy");

// Creates a "myJsonObj" object and adds the book to it
JsonObject myJsonObj = getJsonManager().create();
myJsonObj.set("user.books[2]", book);

The myJsonObj object is now :

{
    "user" : {
        "books" : [
            null,
            null,
            {
                "title" : "The Hitchhiker's Guide to the Galaxy"
            }
        ]
    }
}

Notice that, in that example, the user object didn't exist when we inserted the book! When you add an element using a JsonPath, all the parents are automatically created, if required.

If you really need to insert an element in a JsonObject using a key containing some of the JsonPaths special characters (which are ".", "[" and "]"), and without that key being parsed as a JsonPath, you can do so by using the setNoKeyParsing(...) method. For example :

// Creates a book object
JsonObject book = getJsonManager().create();
book.set("title", "The Hitchhiker's Guide to the Galaxy");

// Creates a "myJsonObj" object and adds the book to it
// using an unparsed key!
JsonObject myJsonObj = getJsonManager().create();
myJsonObj.setNoKeyParsing("user.books[2]", book);

The myJsonObj object is now :

{
    "user.books[2]" : {
        "title" : "The Hitchhiker's Guide to the Galaxy"
    }
}

As you can see, the "user.books[2]" key has been taken as is, without being parsed as a JsonPath.