Jackson how to transform JsonNode to ArrayNode without casting? - java

I am changing my JSON library from org.json to Jackson and I want to migrate the following code:
JSONObject datasets = readJSON(new URL(DATASETS));
JSONArray datasetArray = datasets.getJSONArray("datasets");
Now in Jackson I have the following:
ObjectMapper m = new ObjectMapper();
JsonNode datasets = m.readTree(new URL(DATASETS));
ArrayNode datasetArray = (ArrayNode)datasets.get("datasets");
However I don't like the cast there, is there the possibility for a ClassCastException?
Is there a method equivalent to getJSONArray in org.json so that I have proper error handling in case it isn't an array?

Yes, the Jackson manual parser design is quite different from other libraries. In particular, you will notice that JsonNode has most of the functions that you would typically associate with array nodes from other API's. As such, you do not need to cast to an ArrayNode to use. Here's an example:
JSON:
{
"objects" : ["One", "Two", "Three"]
}
Code:
final String json = "{\"objects\" : [\"One\", \"Two\", \"Three\"]}";
final JsonNode arrNode = new ObjectMapper().readTree(json).get("objects");
if (arrNode.isArray()) {
for (final JsonNode objNode : arrNode) {
System.out.println(objNode);
}
}
Output:
"One"
"Two"
"Three"
Note the use of isArray to verify that the node is actually an array before iterating. The check is not necessary if you are absolutely confident in your datas structure, but its available should you need it (and this is no different from most other JSON libraries).

In Java 8 you can do it like this:
import java.util.*;
import java.util.stream.*;
List<JsonNode> datasets = StreamSupport
.stream(obj.get("datasets").spliterator(), false)
.collect(Collectors.toList())

I would assume at the end of the day you want to consume the data in the ArrayNode by iterating it. For that:
Iterator<JsonNode> iterator = datasets.withArray("datasets").elements();
while (iterator.hasNext())
System.out.print(iterator.next().toString() + " ");
or if you're into streams and lambda functions:
import com.google.common.collect.Streams;
Streams.stream(datasets.withArray("datasets").elements())
.forEach( item -> System.out.print(item.toString()) )

Is there a method equivalent to getJSONArray in org.json so that I have proper error handling in case it isn't an array?
It depends on your input; i.e. the stuff you fetch from the URL. If the value of the "datasets" attribute is an associative array rather than a plain array, you will get a ClassCastException.
But then again, the correctness of your old version also depends on the input. In the situation where your new version throws a ClassCastException, the old version will throw JSONException. Reference: http://www.json.org/javadoc/org/json/JSONObject.html#getJSONArray(java.lang.String)

Obtain an iterator by calling the JsonNode's iterator() method, and go on...
JsonNode array = datasets.get("datasets");
if (array.isArray()) {
Iterator<JsonNode> itr = array.iterator();
/* Set up a loop that makes a call to hasNext().
Have the loop iterate as long as hasNext() returns true.*/
while (itr.hasNext()) {
JsonNode item=itr.next();
// do something with array elements
}
}

Related

Jackson Unmarshall custom object instead of LinkedHashMap

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<...>>.

Getting a SubArray of Fasterxml Jackson ArrayNode

I need to get a sub-array of an ArrayNode object in fasterxml jackson in Java.
To be more clear,
I have a fasterxml jackson ArrayNode object which contains for example let's say 100 objects.
I have a limit parameter for example let's say 5.
Can do it in a very primitive way as indicated below,
ArrayNode arrayNodeRecProducts = (ArrayNode) recProducts;
int arrayNodeSize = arrayNodeRecProducts.size();
if (limit >= 0 && limit < arrayNodeSize) {
while (arrayNodeRecProducts.has(limit)) {
arrayNodeRecProducts.remove(limit);
}
}
The "recProducts" object casted to ArrayNode type is a fasterxml jackson JsonNode and contains an array.
Above works but quite inefficient as the inner while loop runs for "arrayNodeSize - limit" number of times in removing the ArrayNode objects one by one.
Is there a sub-array operation which we can perform on the ArrayNode or the casted JsonNode itself?
Thanks and Regards..
Thanks "henrik" for your answer and you were correct in that Jackson doesn't support such a functionality for ArrayNodes. So what I did was I downloaded the Jackson databind codebase and looked inside the hood why they are not providing such a SubArray functionality for ArrayNodes (Please be informed that I am referring to databind 2.3.2).
Internally, Jackson is maintaining the ArrayNode in a JsonNode List as below,
private final List<JsonNode> _children = new ArrayList<JsonNode>();
To my surprise, for some reason I cannot understand, they have not provided a SubArray operation which could be easily accomplished by using the subList method of this contained list. For example as below,
public List<JsonNode> subArray(int fromIndex, int toIndex) {
return _children.subList(fromIndex, toIndex);
}
Above method would have saved me from the trouble I was facing but it is simply not included in the library.
So what I did in my codebase is to simply hack into this private list in runtime using reflection and invoke the subList operation at runtime as below.
ArrayNode arrayNodeRecProducts = (ArrayNode) recProducts;
if (limit >= 0 && limit < arrayNodeRecProducts.size()) {
Field innerArrayNode = ArrayNode.class.getDeclaredField("_children");
innerArrayNode.setAccessible(true);
List<JsonNode> innerArrayNodeChildNodes = (List<JsonNode>) innerArrayNode.get(arrayNodeRecProducts);
List<JsonNode> limitedChildNodes = innerArrayNodeChildNodes.subList(0, limit);
innerArrayNode.set(arrayNodeRecProducts, limitedChildNodes);
}
I know that the above code will not work in all situations but for my situation it is working fine.
At the same time, I know this is a violation of our well guarded OO principle Encapsulation, but I can live with that for reasons explained in below post.
Dosen't Reflection API break the very purpose of Data encapsulation?
There is no such support build in Jackson.
I would create a new ArrayNode and fill it until the limit, that would be more efficient in most cases:
ArrayNode limited = objectMapper.createArrayNode();
for(JsonNode e : src) {
limited.add(e);
if (limited.size() == limit) {
break;
}
}

Parsing Json response to a different Java object each time

I am using Jersey as a client to parse JSON into Java objects,
The problem is that I am using a service that returns different types of responses that should be mapped to a different Java object each time, so I need a way to step into the parsing process and make an arbitrary decision to tell Jersey what is the exact type of object to parse each time.
EDIT:
For example if I have Java Classes A, B, and C and the Json Response as follows:
Data{
-list {
-0 :{Result should be mapped to A}
-1 :{Result should be mapped to B}
-2 :{Result should be mapped to C}
}
}
and the list is ArrayList (or can be ArrayList of a super class for the three classes). Now when I ask Jersey to parse this JSON response, It will find an ArrayList when handling list and dosen't know what's the exact type of the object to parse into, so it convert the data inside -0, -1, -2 to a linkedHashMap as a key/value pairs.
I use jackson in a jersey client to map json to a hashMap but the mapper will work for pojo's as well. Hope the following helps.
Get the list elements into an array/list.
Loop through and determine the correct class for each element.
Pass each list element and its respective class name to a method that handles the mapping and returns an object.
import ...
import com.fasterxml.jackson.databind.ObjectMapper;
public class RequestProcessor {
void jerseyClient(){
//rest request
WebResource resource = ...
ClientResponse responseContent = resource.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
List list = parseJSonResponse(responseContent);
for (String jsonListElement : list){
//determine correct class for jsonListElement
//Pass jsonListElement and class name to mapper method
Object obj = mapElementToObject(jsonListElement , className);
//do something with obj
}
}
private Object mapElementToObject(String jsonString, String className){
ObjectMapper mapper = new ObjectMapper();
Object obj = mapper.readValue(jsonString, Class.forName(className);
return obj;
}
private List parseJsonResponse(responseContent){
//use regexp to replace unnecessary content in response so you have a string
//that looks like -0:{element values}-1:{element values}-2:{element values}
//then split the string on "-.*\\:" into a array/list
//return the list
}
}

Can't parse JSON array of arrays to LinkedHashMap in Jackson

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);
}

Logical solution for creating a JSON structure

I am not sure if it possible or not but I think it can be done using JSONArray.put method.
Heres my problem:
I have got two lists:
ArrayList<Students> nativeStudents;
ArrayList<transferStudents> transferStudents = nativeStudents.getTransferStudentsList();
The JSON that I generate with transferStudents list is right here: http://jsfiddle.net/QLh77/2/ using the following code:
public static JSONObject getMyJSONObject( List<?> list )
{
JSONObject json = new JSONObject();
JsonConfig config = new JsonConfig();
config.addIgnoreFieldAnnotation( MyAppJsonIgnore.class );
if( list.size() > 0 )
{
JSONArray array = JSONArray.fromObject( list, config );
json.put( "students", array );
}
else
{
//Empty Array
JSONArray array = new JSONArray();
json.put( "students",
array );
}
return json;
}
Now what I want to get is JSON data with following structure: http://jsfiddle.net/bsa3k/1/ (Notice the tempRollNumber field in both array elements).
I was thinking of doing: (The if condition here is used for a business logic)
if(transferStudents.getNewStudentDetails().getRollNumber() == nativeStudents.getNativeStudentDetails.getStudentId()){
json.put("tempRollNumber", transferStudents.getNewStudentDetails().getRollNumber());
}
but this would add tempRollNumber outsite the array elements, I want this JSON element to be part of every entry of students array.
PS: I cant edit the transferStudents class in order to add tempRollNumber field.
Since no one has come up with anything better I'll turn my comments above into an answer.
The best way to handle this is to create an object model of your data and not create the JSON output yourself. Your app server or container can handle that for you.
Though you cannot change the objects you receive in the List you can extend the object's class to add your own fields. Those fields would then appear in the JSON when you marshall it.

Categories