{#==========================================
Docs : "JsonObjects"
==========================================#}
Here's a quick example of using a
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.
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 :
JsonObjects and JsonArrays
Getters are provided for all of those native types :
String getString(String key)
Integer getInteger(String key)
JsonObject getJsonObject(String key)
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.
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();
//...
}
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");
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()).
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()
boolean isElementExists(String jsonPath)
JsonObject contain an element at the specified
JsonPath?
JsonObject merge(JsonObject jsonObj, boolean clone)JsonObject merge(Map<String, Object> map, boolean clone)
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)
jsonPath.
JsonObject getJsonObjectOrEmpty(String jsonPath)JsonArray getJsonArrayOrEmpty(String jsonPath)
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)
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)
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)
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>.
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()
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()
JsonArray to a List<String>.
To achieve this, the toString() method will be called on any non null
element of the array.
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 :
".".
For example : "user.car".
JsonArray
you use "[X]", where X is the position of the element in
the array (the first index is 0).
For example : "books[3]".
".", "[" or "]"), you need to surround it
with brackets. For example : "user['a key with spaces']".
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);
myJsonObj object is now :
{
"user" : {
"books" : [
null,
null,
{
"title" : "The Hitchhiker's Guide to the Galaxy"
}
]
}
}
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);
myJsonObj object is now :
{
"user.books[2]" : {
"title" : "The Hitchhiker's Guide to the Galaxy"
}
}
"user.books[2]" key has been taken
as is, without being parsed as a JsonPath.