All Downloads are FREE. Search and download functionalities are using the official Maven repository.

templates.docs.jsonObjects.html Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
{#==========================================
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 :

  • String
  • Integer
  • Long
  • Float
  • Double
  • BigDecimal
  • Boolean
  • byte[] (serialized as a base 64 encoded String)
  • Date
  • Instant
  • Other JsonObjects and JsonArrays

Getters are provided for all of those native types :

  • String getString(String key)
  • Integer getInteger(String key)
  • JsonObject getJsonObject(String key)
  • ...
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 :

  • int size()
    The number of properties on the object.
  • boolean contains(String jsonPath)
    Does the JsonObject contain an element at the specified JsonPath?
  • JsonObject merge(JsonObject jsonObj, boolean clone)
    JsonObject merge(Map<String, Object> map, boolean clone)
    Merges a external JsonObject or a plain Map<String, Object> into the JsonObject. You can specify if the added elements must be cloned or not (in case some are JsonObject or JsonArray).
  • JsonObject remove(String jsonPath)
    Removes an element using its jsonPath.
  • JsonObject getJsonObjectOrEmpty(String jsonPath)
    JsonArray getJsonArrayOrEmpty(String jsonPath)
    Returns the JsonObject or JsonArray at the specified JsonPath or returns an empty instance if it's not found. This allows you to try to get a deep element without any potential NullPointerException. For example :

    // This won't throw any NPE, even if the "myArrayKey"
    // array or its first element don't exist
    String value = obj.getJsonArrayOrEmpty("myArrayKey")
                      .getJsonObjectOrEmpty(0)
                      .getString("someKey", "defaultValue");
    

  • [TYPE] getArrayFirst[TYPE](String jsonPath, String defaultValue)
    For all types native to JsonObject, a getArrayFirst[TYPE](...) method exists. With those methods, you can get the first element of a JsonArray located at the specified JsonPath. This is useful in situations where you know the array only contains a single element :

    // This :
    String value = obj.getArrayFirstString("myArrayKey", "defaultValue")
    
    // ... is the same as :
    String value = obj.getJsonArrayOrEmpty("myArrayKey").getString(0, "defaultValue")
    

  • void transform(String jsonPath, ElementTransformer transformer)
    Applies an ElementTransformer to the element located at the specify JsonPath. This is used to modify an element without having to extract it first. For example, the provided JsonObject#trim(String jsonPath) method exactly does this : it internally calls transform(...) and pass it an ElementTransformer which trims the target element.
  • String toJsonString(boolean pretty)
    Converts the JsonObject object to a Json string. If pretty is true, the resulting Json will be formatted.
  • Map<String, Object> convertToPlainMap()

    If you need to use the elements of a JsonObject in some code that doesn't know how to handle JsonObjects, you can convert it to a plain Map<String, Object>. Spincast does this, for example, to pass the elements of the response model to the Template Engine when it's time to evaluate a template and send an HTML page. Pebble, the default templating Engine, knows nothing about JsonObjects but can easily deal with a plain Map<String, Object>.

    Note that all JsonObject children will be converted to Map<String, Object> too, and all JsonArray children to List<Object>.

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 :

  • List<Object> convertToPlainList()
    Converts the JsonArray to a plain List<Object>. All JsonObject children will be converted to Map<String, Object>, and all JsonArray children to List<Object>.
  • List<String> convertToStringList()
    Converts the JsonArray to a List<String>. To achieve this, the toString() method will be called on any non null element of the array.

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 :

  • To access a child you use a ".". For example : "user.car".
  • To access the element of a JsonArray you use "[X]", where X is the position of the element in the array (the first index is 0). For example : "books[3]".
  • If a key contains spaces, or a reserved character (".", "[" or "]"), you need to surround it with brackets. For example : "user['a key with spaces']".
  • That's it!

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.





© 2015 - 2024 Weber Informatics LLC | Privacy Policy