I'm just starting using the Jackson JSON library. Jackson is a very powerful library, but it has a terribly extensive API. A lot of things can be done in multiple ways. This makes it hard to find your way in Jackson - how to know what is the correct/best way of doing things?
Why would I use this solution:
String json = "{\"a\":2, \"b\":\"a string\", \"c\": [6.7, 6, 5.6, 8.0]}";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readValue(json, JsonNode.class);
if (node.isObject()) {
ObjectNode obj = mapper.convertValue(node, ObjectNode.class);
if (obj.has("a")) {
System.out.println("a=" + obj.get("a").asDouble());
}
}
Over a solution like this:
String json = "{\"a\":2, \"b\":\"a string\", \"c\": [6.7, 6, 5.6, 8.0]}";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
if (node.isObject()) {
ObjectNode obj = (ObjectNode) node;
if (obj.has("a")) {
System.out.println("a=" + obj.get("a").asDouble());
}
}
Or over solutions that I came across using JsonFactory and JsonParser and maybe even more options...
It seems to mee that mapper.readValue is most generic and can be used in a lot of cases: read to JsonNode, ObjectNode, ArrayNode, PoJo, etc. So why would I want to use mapper.readTree?
And what is the best way to convert a JsonNode to an ObjectNode? Just cast to ObjectNode? Or use something like mapper.convertValue?
readValue() can be used for any and all types, including JsonNode. readTree() only works for JsonNode (tree model); and is added for convenience.
Note that you NEVER want to use your first example: it is equivalent to writing out your node as JSON, then reading it back -- just cast it.
Read value can be used for your own java classes:
public class Foo {
private int a;
private String b;
private double[] c;
// getters/setters
}
String json = "{\"a\":2, \"b\":\"a string\", \"c\": [6.7, 6, 5.6, 8.0]}";
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(json, Foo.class);
i.e. You may choose readTree when you do not know exact type of the Object, and readValue when you know the Object type for sure.
These methods have different runtime when you parsing same JSON
The fastest way is using readValue() with type reference HashMap:
TypeReference<HashMap<String, Object>> typeRef= new TypeReference<>() {};
HashMap<String, Object> parsedJson = objectMapper.readValue(json,typeRef);
Using readTree() and writing manual parser with JsonNode objects also wiil give you fast runtime.
Additional benefit of using readTree(): you can use stream parallel when parsing ArrayNode objects, which will boost runtime on multicore processors.
The slowest way is using readValue() with POJO class:
YourPOJO parsedJson = objectMapper.readValue(json, YourPOJO.class);
This is approximate runtime of each parsing way on same JSON:
Auto parsing raw map with ObjectMapper readValue() runtime: 34 ms
Auto parsing type reference map with ObjectMapper readValue() runtime: 6 ms
Auto parsing POJO with ObjectMapper readValue() runtime: 51 ms
Manual sequential parsing with ObjectMapper readTree() runtime: 18 ms
Manual stream parallel parsing with ObjectMapper readTree() runtime: 10 ms
I have done my example and tests here:
https://github.com/EvgenyUmansky/java-json-xml-processing/blob/master/src/test/java/JacksonTest.java
Another tests here:
https://gist.github.com/varren/3b7468a1d7abadad4958fe6bd7d42d44
Related
I have following class that is POJO of JSON document:
public class ApiResponse {
private boolean success;
private List<ApiRecord> data;
}
I deserialize object using ObjectMapper class in the following way:
var apiResponse = mapper.readValue(target, ApiResponse.class);
I want to make Jackson treat every ApiRecord deserialization failure as not failure of whole deserialization process but instead just get a list that contains only valid parsed objects, so the wrong elements of 'data' field are acceptable (not appearing in POJO list) and not blocking the rest ones.
Any idea on how to do this?
Do one thing add one property to the mapper object.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
var apiResponse = mapper.readValue(target, ApiResponse.class);
My solution to this problem was to write my own deserializer which in case of failure returns null for array's element and tag the field that needs to use this deserializer with #com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = SomeClass.class).
Objective: deep copy (or clone) of a Java object
One of the suggested ways (almost everywhere) to do it is using Jackson:
MyPojo myPojo = new MyPojo();
ObjectMapper mapper = new ObjectMapper();
MyPojo newPojo = mapper.readValue(mapper.writeValueAsString(myPojo), MyPojo.class);
Question: is the following better? in terms of performance? is there any drawbacks?
MyPojo myPojo = new MyPojo();
ObjectMapper mapper = new ObjectMapper();
MyPojo newPojo = mapper.treeToValue(mapper.valueToTree(myPojo), MyPojo.class);
Answered by Tatu Saloranta:
Second way should be bit more efficient since it only creates and uses
logical token stream but does not have to encode JSON and then decode (parse) it to/from token stream. So that is close to optimal regarding
Jackson.
About the only thing to make it even more optimal would be to directly
use TokenBuffer (which is what Jackson itself uses for buffering). Something like:
TokenBuffer tb = new TokenBuffer(); // or one of factory methods
mapper.writeValue(tb, myPojo);
MyPojo copy = mapper.readValue(tb.asParser(), MyPojo.class);
This would further eliminate construction and traversal of the tree model. I don't know how big a difference it'll make, but is not much more code.
Thanks Tatu :)
I have copied my "valueNode" during iteration and made a copy of it using ObjectMapper now "copyJsonNode" is the replica of "valueNode" which i need for further implementation.
if (entry.getKey().equalsIgnoreCase("admin")) {
JsonNode valueNode = entry.getValue();
String copyjson = valueNode.toString();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode copyJsonNode = objectMapper.readTree(copyjson);
....}
I have a Java object Results:
public class MetaData {
private List<AttributeValue<String,Object>> properties
private String name
...
... getters/setters ...
}
The AttributeValue class is a generic key-value class. It's possible different AttributeValue's are nested. The (value) Object will then be another AttributeValue and so forth.
Due to legacy reasons the structure of this object cannot be altered.
I have my JSON, which I try to map to this object.
All goes well for the regular properties. Also the first level of the list is filled with AttributeValues.
The problem is the Object. Jackson doesn't know how to interpret this nested behavior and just makes it a LinkedHashMap.
I'm looking for a way to implement custom behavior to tell Jackson this has to be a AttributeValue-object instead of the LinkedHashMap.
This is how I'm currently converting the JSON:
ObjectMapper om = new ObjectMapper();
MetaData metaData = om.readValue(jsonString, new TypeReference<MetaData>(){});
And this is example JSON. (this is obtained by serializing an existing MetaData object to JSON, I have complete control over this syntax).
{
"properties":[
{
"attribute":"creators",
"value":[
{
"attribute":"creator",
"value":"user1"
},{
"attribute":"creator",
"value":"user2"
}
]
},{
"attribute":"type",
"value": "question"
}
],
"name":"example"
}
(btw: I've tried the same using GSON, but then the object is a StringMap and the problem is the same. Solutions using GSON are also welcome).
edit In Using Jackson ObjectMapper with Generics to POJO instead of LinkedHashMap there is a comment from StaxMan:
"LinkedHashMap is only returned when type information is missing (or if Object.class is defined as type)."
The latter seems to be the issue here. Is there a way I can override this?
If you have control over the serialization, try calling enableDefaultTyping() on your mapper.
Consider this example:
Pair<Integer, Pair<Integer, Integer>> pair = new Pair<>(1, new Pair<>(1, 1));
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(pair);
Pair result = mapper.readValue(str, Pair.class);
Without enableDefaultTyping(), I would have str = {"k":1,"v":{"k":1,"v":1}} which would deserialize to a Pair with LinkedHashMap.
But if I enableDefaultTyping() on mapper, then str = {"k":1,"v":["Pair",{"k":1,"v":1}]} which then perfectly deserializes to Pair<Integer, Pair<...>>.
I have a new JsonNode that I created
JsonNode jNode = new ObjectCodec().createObjectNode();
with this node, how do I then add key value pairs within so that I can construct this new node with the new values? What I read in http://www.cowtowncoder.com/blog/archives/2011/08/entry_460.html mentioned about using
jNode.with("newNode").put("key1","value1");
But looking at the APIs for Jackson's JsonNode (v1.8) does not show any method as such.
These methods are in ObjectNode: the division is such that most read operations are included in JsonNode, but mutations in ObjectNode and ArrayNode.
Note that you can just change first line to be:
ObjectNode jNode = mapper.createObjectNode();
// version ObjectMapper has should return ObjectNode type
or
ObjectNode jNode = (ObjectNode) objectCodec.createObjectNode();
// ObjectCodec is in core part, must be of type JsonNode so need cast
I've recently found even more interesting way to create any ValueNode or ContainerNode (Jackson v2.3).
ObjectNode node = JsonNodeFactory.instance.objectNode();
I' m developing an Android REST client. We use JSON as data exchange format, so I use a Jackson parser. I get different Json responses from the server like simple arrays:
{"user_id":"332","user_role":"1"}
or something else. All these stuff I parse to LinkedHashMap<String, Object> and everything works perfectly but when I got this response from the server:
[ { "user_id":"352",
"user_role":"expert",
"name":"Test 12-18",
"description":"Test" },
{ "user_id":"263",
"user_role":"novice lab",
"name":"Tom's Desk",
"description":"Desk"}
]
I got null: {} after parsing.Here is my code where i use Jackson:
ObjectMapper mapParametersToJSON = new ObjectMapper();
String serverResponseBody = responseFromServer.getBody();
LinkedHashMap<String, Object> resultofOperation = new LinkedHashMap<String,
Object>();
TypeReference<LinkedHashMap<String,Object>> genericTypeReferenceInformation = new
TypeReference<LinkedHashMap<String,Object>>() {};
try {
resultofOperation = mapParametersToJSON.readValue(serverResponseBody,
genericTypeReferenceInformation);
So, why Jackson failed to parse this? How can I fix this?
Others have suggested the problem, but solutions are bit incomplete. If you need to deal with JSON Objects and Arrays, you can either bind to java.lang.Object, check the type:
Object stuff = objectMapper.readValue(json, Object.class);
and you will get either List or Map (specifically, ArrayList or LinkedHashMap, by default; these defaults can be changed).
Or you can do JSON trees with JsonNode:
JsonNode root = objectMapper.readTree(json);
if (root.isObject()) { // JSON Object
} else if (root.isArray()) { ...
}
latter is often more convenient.
One nice thing is that you can still create regular POJOs out of these, for example:
if (root.isObject()) {
MyObject ob = objectMapper.treeToValue(MyObject.class);
}
// or with Object, use objectMapper.convertValue(ob, MyObject.class)
so you can even have different handling for different types; go back and forth different representations.
The first JSON in your question is a map, or an object. The second is an array. You're not parsing an array, you're parsing a map.
You need to do something like this:
List<MyClass> myObjects = mapper.readValue(jsonInput, new TypeReference<List<MyClass>>(){});
Almost identical question with answer here.
In JSON the {"key": "value"} is Object and the ["this", "that"] is Array.
So, in case when you're receiving the array of objects you should use something like List<Map<Key, Value>>.
You are facing an error, because [] construction can't be translated into Map reference, only in List or array.
I would recommend do it something in this way:
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String,String>> parsedResult = objectMapper.reader(CollectionType.construct(LinkedList.class, MapType.construct(LinkedHashMap.class, SimpleType.construct(String.class), SimpleType.construct(String.class)))).readValue(serverResponseBody);
//if you need the one result map
Map<String, String> resultMap = new LinkedHashMap<String, String>();
for (Map<String, String> map: parsedResult){
resultMap.putAll(map);
}