A fluent Java API for manipulating json data structures
I'm not actively using/developing this any more and have been using Kotlin in the last few years. So, if you need any changes to this project, feel free to fork and DIY.
JsonJ is a fast performing addon library to Jackson for working with schemaless json in Java. JsonJ backs the very simple json types using a fluent API.
Jackson's streaming parser is used for parsing and it uses things like generics, Java 8 streams, varargs, the Collections framework and other modern Java constructs to make life easier dealing with json.
As of 2.43, JsonJ also fully supports the Jackson ObjectMapper. Simply use the jsonj types (JsonObject
, JsonArray
, and JsonPrimitive
) and jackson will serialize/deserialize as you are used to. So you can mix jsonj objects this with using strongly typed model classes.
The core use case for jsonj is quickly prototyping with complex json data structures without getting bogged down in creating endless amounts of model classes. Model classes are nice if you have a stable, and well defined domain but can be a royal pain in the ass when this is not the case and your json is relatively complex and more or less schema less.
After lots of frustration babysitting maven central deploys; we now release instantly on jitpack:
Older releases may still be available on maven central. If a recent release on maven central is critical to you, ping me.
Note. always check for the latest version. I do not always update the readme.
Java 8 is required as of version 2. For Java 7 and earlier, you can use the 1.x releases.
The license is the MIT license, a.k.a. the expat license. The rationale for choosing this license is that I want to maximize your freedom to do whatever you want with the code while limiting my liability for the damage this code could potentially do in your hands. I do appreciate attribution but not enough to require it in the license (beyond the obligatory copyright notice).
JsonJ has a ton of features and there's plenty more to discover beyond what is shown here.
JsonJ uses Jackson's streaming parser with a custom handler to parse json into JsonElement instances. This is the fastest way to parse json.
// Create a parser (you need just 1 instance for your application)
JsonParser parser = new JsonParser();
// Parse some json
JsonElement element = parser.parse("[1,2,3]");
// when using streams, we assume you are using UTF-8
JsonObject object = parser.parseObject(inputStream);
// or just use a reader
JsonElement element = parser.parse(reader);
// Jsonj also supports yaml, bson, hocon, and several other tree like syntaxes through Jackson's plugin infrastructure.
// for example:
YamlParser yamlParser = new YamlParser();
JsonElement anotherElement = yamlParser.parse(inputStream)
As of 2.43, the Jackson ObjectMapper is fully supported.
JsonObject myObject=...;
String serialized = objectMapper.writeValueAsString(myObject);
JsonObject deSerialized = objectMapper.readValue(serialized, JsonObject.class);
// myObject.equals(deSerialized);
This also means you can mix jsonj with pojos in your models:
For example, this will work as you'd hope:
class Foo {
private String attribute;
private int anotherAttribute;
private JsonObject nestedJsonj;
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public int getAnotherAttribute() {
return anotherAttribute;
}
public void setAnotherAttribute(int anotherAttribute) {
this.anotherAttribute = anotherAttribute;
}
public JsonObject getNestedJsonj() {
return nestedJsonj;
}
public void setNestedJsonj(JsonObject nestedJsonj) {
this.nestedJsonj = nestedJsonj;
}
}
Foo foo = new Foo();
foo.setAttribute("Hi wrld");
foo.setAnotherAttribute(42);
JsonObject nested = object(
field("meaning_of_life",42),
field("a",42.0),
field("b",true),
field("c",array(42,"foo",3.14,true,null)),
field("d",object(field("a",1)))
);
foo.setNestedJsonj(nested);
String serialized = objectMapper.writeValueAsString(foo);
JsonObject object = parser.parseObject(serialized);
assertThat(object.getString("attribute")).isEqualTo("Hi wrld");
assertThat(object.getInt("anotherAttribute")).isEqualTo(42);
assertThat(object.getObject("nestedJsonj")).isEqualTo(nested);
// or of course
Foo fooParsed = objectMapper.readValue(serialized, Foo.class);
// etc.
JsonObject o=parser.parse(inputStream);
// Jsonj objects know how to serialize themselves
String oneLine=o.toString();
// pretty printing is nice when reading json
String pretty=o.prettyPrint();
// you can also serialize straight to a writer of course.
o.serialize(writer);
// easy object and array creation using simple static factory methods, varargs, and smart polymorph argument processing
JsonObject o = object(
field("hello","world"),
field("another_field",array("of","mixed","types",42, 42.0,true,false,null)),
field("nested",object(field("nested_field",42)))
);
// basic element lookup
JsonElement e = o.get("hello"); // JsonPrimitive with value "world"
JsonElement e2 = o.get("nested"); // JsonObject
JsonElement e3 = o.get("nested","nested_field"); // JsonPrimitive with value 42
// type safe value extraction
Integer value = o.getInt("nested","nested_field"); // 42
Integer noValue = o.getInt("nested","idontexist"); // null
Integer defaultValue = o.getInt("idontexist", 42); // null
Optional<Integer> o.maybeGetInt("nested","idontexist"); // Optional.empty();
// there are several variations of these for strings, booleans, longs, doubles, floats, and Number.
// convert JsonElements
JsonElement e= ....
int val=e.asInt(); // throws JsonTypeMismatchException if the element is not a JsonPrimitive or cannot be converted to an int.
JsonObject object = e.asObject(); // throws JsonTypeMismatchException if e.isObject() == false
JsonArray object = e.asArray(); // throws JsonTypeMismatchException if e.isArray() == false
// on the fly nested object and array creation
//creates a five level deep array at o.l1.l2.l3.l4.a1=[1,2,3]
o.getOrCreateObject("l1","l2","l3").getOrCreateArray("l4","a1").add(1,2,3);
// adds three more elements to the array
o.getOrCreateObject("l1","l2","l3").getOrCreateArray("l4","a1").add(4,5,6);
// objects are Map<String,JsonElement>
// so this works, as well as all of the Map API in java 8.
object.forEach((key,element) -> {...}); // easy iteration over entries in the Map<String,JsonElement)
// likewise, JsonArray implements List<JsonElement>
// so you can do stream processing
JsonArray result = object.getOrCreateArray("array").stream().map(element -> element.asObject().getString("nested_field")).collect(JsonJCollectors.array());
// Java has sets and they are useful, JsonSet implements Set<Element>
// asSet converts the list to a set and removes duplicate elements
// inserts of duplicate elements replace the old value
JsonSet set = object.getOrCreateArray("array").asSet();
// by default JsonSet uses element equals for identity but you can change this
JsonSet set2 = object.getOrCreateArray("array")
.asSet()
// return true if left and right elements are equal, false otherwise);
.applyIdStrategy((left,right) -> ...);
// or use the simple id field strategy for the common case where you
// have an array of objects with an id field
// compares objects on their id field, throws JsonTypeMismatchException
// if elements are not objects.
JsonSet set3 = object.getOrCreateArray("array").asSet().applyIdStrategy("id");
JsonJ is all about making life easy. So it uses varargs wherever it makes sense and polymorphism to make most type conversions unnecessary.
JsonArray array=array(); // factory method for new JsonArray();
// array() has several polymorph implementations that support varargs as well.
array = array("foo","bar") // creates a new array with two JsonPrimitives.
// JsonArray and JsonSet have several add methods
array.add(primitive(1)); // adds 1
array.add(1); // does the same and converts 1 to a JsonPrimitive for you
// varargs are nice.
array.add(2,3,4);
array.add(true) // adds a JsonPrimitive with value true
array.add("hello") // add a string primitive
JsonObject map=new JsonObject();
// these all do the same ...
map.put("field", primitive(42));
map.put("field",42);
map.put("field",new JsonPrimitive(42));
// Maps are lists of entries, so you can add entries.
// use the field factory method to create entries
// field is polymorph and tries to do the right thing, just like JsonArray add.
Entry<String,JsonElement> entry=field("field",42);
map.add(entry);
map.add(field("1",1),field("2",2)); // add supports varargs of course.
You can find the array
, set
, field
, and object
methods in the JsonBuilder
if your IDE supports this (eclipse), you'll want to configure this as a favourite to add the static imports for this.
There are convenient methods to iterate over different types of elements in an array. This saves you from manual object casting/conversion.
ints(), longs(), strings(), arrays(), and objects() are supported for this.
// [1,2,3]
for(int i: array(1,2,3).ints()) {
..
}
Or you can use the new Java 8 streams:
// do somthing for each entry in the dictionary
object.forEach(k,v) -> {
...
}
// or stream over elemnts in a json Array
someArray.stream().forEach(element -> ...); // as inherited from ArrayList
someArray.streamInts().forEach(i -> ...); // same but maps elements to ints
someArray.streamObjects().forEach(object -> ...); // maps to JsonObject
// etc
// extract the name fields of objects inside an array and collect them in a JsonSet
JsonSet names = array.streamObjects().map(o -> o.getString("name")).collect(JsonJCollectors.set());
Of course all this convenience means that sometimes things don't work. All the exceptions in jsonj are unchecked, so there is no need to litter your business logic with try .. catch.
array("foo").ints() // throws JsonTypeMismatchException
array(1,2,3).asObject() // throws JsonTypeMismatchException
There are several reasons why you might like jsonj
class Foo {JsonObject someNestedField; int bar; String foo}
mapped as you would want. This allows you to have hybrid schema less Jsonj and strongly typed pojos.Stream<JsonObject> JsonParser.parseJsonLines(Reader r)
There are probably more reasons you can find to like JsonJ, why not give it a try? Let me know if you like it (or not). Let me know it should be changed in some way.
JsonJ implements several things that ensure it uses much less memory than might otherwise be the case:
JsonObject
implementations that have different memory and performance characteristics. You can control the behavior from the parser settings and mostly you don't need to worry about which is used.JsonObject
. This is the default implementation. It uses a custom Map
implementation with two arrays for the keys and values. This ensures insertion order is reflected when iterating and performs well for small objects.MapBasedJsonObject
. Because neither JsonObject
nor SimpleIntMapJsonObject
scale to large numbers of keys, the parser switches to this implementation automatically when the number of keys exceeds the configurable threshold (default is 100). This implementation uses a simple LinkedHashMap instead. This uses way more memory but scales to much larger amounts of keys than either of the other implementations.SimpleIntMapJsonObject
and JsonObject
use UTF8 byte arrays for storing String primitive values. This is more efficient than Java's own String class, which uses utf-16.asInt()
, asDouble()
, etc on quoted numeric values without failing with a type exception.#
.Optional
. You can use this as an alternative to the existing accessors that may return null.Stream<JsonObject>
that you can process without having to buffer everything in memory.MapBasedJsonObject
that can be configured to use any kind of Map
. By default this uses a LinkedHashMap
which comfortably handles millions of keys (at the expense of using more memory). The parser handler now automatically converts objects over 100 keys to this and there is a new toMapBasedJsonObject()
method that can convert existing JsonObjects (by cloning them). WARNING: this release should be considered broken. Use 2.26.add(String...s)
method.parseObject
methodJsonBuilder
from the actual JsonElement
. Useful to convert json fragments into code (e.g. a complex elastic search query).GeoJsonSupport
$
as an alias for array()
as well. Yay polymorphism! JsonObject o=$(_("foo","bar"), _("list", $(1,2,3)))
$
and _
aliases for object
and field
: JsonObject o=$(_("foo","bar"), _("list", array(1,2,3)))
now works as well.JsonObject o=object(field("foo","bar"), field("list", array(1,2,3)))
now works. You can also add multiple field entries to an existing object or take the entrySet of an existing object and add those entries as fields to an object.JsonArray
so you can foreach over elements of that type without any conversions. We already had objects(). for example: for(String s: jsonArray.strings()) {
System.out.println(s);
}
null
values to be added as json null instead of rejecting them with an illegal argument exception.