All subclasses of ValueNode
in Jackson (the JSON library) have different methods for getting the underlying value object e.g. IntNode
has getIntValue
, BooleanNode
has getBooleanValue
and so on.
Why is there no generic/polymorphic method called simply getValue
which just returns an Object
and where that Object
is either Integer
or Boolean
, etc., based on the type of the node the method is called on?
Or ... is there such a method actually? I need such a method for my purposes but seems the library designers did not find that adding such a method would be useful. Or ... is that method missing on purpose, for some reason?
My purpose: In the code below I am traversing the tree and generating a structure composed only of HashMap
, Object[]
and Java basic types (like Integer
, Boolean
, etc.). If I had such a method, instead of all these if-else if-else if blocks, I would have just one single method call (in the case when the JsonNode
is a leaf node i.e. a sub-type of ValueNode
). But I don't have such a method in Jackson, it seems. So I had to code all those ugly if-else if-else if blocks.
CODE:
@SuppressWarnings({ "rawtypes", "unchecked" })
private static Object traverse(JsonNode nd) {
if (nd instanceof ObjectNode) {
ObjectNode ndd = (ObjectNode) nd;
HashMap mp = new HashMap();
Iterator<String> it = ndd.getFieldNames();
while (it.hasNext()) {
String s = it.next();
mp.put(s, traverse(ndd.get(s)));
}
return mp;
} else if (nd instanceof ArrayNode) {
ArrayNode ndd = (ArrayNode) nd;
Object[] arr = new Object[ndd.size()];
for (int i = 0; i < ndd.size(); i++) {
arr[i] = traverse(ndd.get(i));
}
return arr;
} else if (nd instanceof NullNode) {
// NullNode ndd = (NullNode)nd;
return null;
} else if (nd instanceof BooleanNode) {
BooleanNode ndd = (BooleanNode) nd;
return ndd.getBooleanValue();
} else if (nd instanceof IntNode) {
IntNode ndd = (IntNode) nd;
return ndd.getIntValue();
} else if (nd instanceof LongNode) {
LongNode ndd = (LongNode) nd;
return ndd.getLongValue();
} else if (nd instanceof DoubleNode) {
DoubleNode ndd = (DoubleNode) nd;
return ndd.getDoubleValue();
} else if (nd instanceof DecimalNode) {
DecimalNode ndd = (DecimalNode) nd;
return ndd.getDecimalValue();
} else if (nd instanceof BigIntegerNode) {
BigIntegerNode ndd = (BigIntegerNode) nd;
return ndd.getBigIntegerValue();
} else if (nd instanceof TextNode) {
TextNode ndd = (TextNode) nd;
return ndd.getTextValue();
}
throw new IllegalStateException("Failed while traversing the JSON tree at node: ||| " + nd.asText() + " |||");
}
The simple readValue API of the ObjectMapper is a good entry point. We can use it to parse or deserialize JSON content into a Java object. Also, on the writing side, we can use the writeValue API to serialize any Java object as JSON output.
JsonNode is Jackson's tree model (object graph model) for JSON. Jackson can read JSON into a JsonNode instance, and write a JsonNode out to JSON. This Jackson JsonNode tutorial will explain how to deserialize JSON into a JsonNode and serialize a JsonNode to JSON.
Gson gson = new Gson(); Type type = new TypeToken<Map<String, String>>(){}. getType(); Map<String,String> map = gson. fromJson(json, type); so you can iterate this map for your purpose.
Write JsonNode to JSONObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode = readJsonIntoJsonNode(); String json = objectMapper. writeValueAsString(jsonNode); The readJsonIntoJsonNode() method is just a method I have created which parses a JSON string into a JsonNode - just so we have a JsonNode to write.
Since you are returning an Object
you can use something like this
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(nd,Object.class);
For this particular test cases it worked
JsonNode nodeBool = BooleanNode.TRUE;
Object objectBool = mapper.convertValue(nodeBool,Object.class);
Boolean returnValBool = (Boolean) objectBool;
System.err.println(returnValBool);
JsonNode nodeDouble = new DoubleNode(3.4);
Object objectDouble = mapper.convertValue(nodeDouble,Object.class);
Double returnValDouble = (Double) objectDouble;
System.err.println(returnValDouble);
and it outputed as expected:
true
3.4
This is an interesting problem :)
I am not sure why there is no generic method. I believe it might have something to do with the fact that they deal with not only object types? well, I am not really qualified to justify their design decision, however I can help you with your generic method.
You are right, you can't just call value(), but you can just call serialize(..,..)
More detail:
All value nodes hold their own value. All value nodes know how to be serialised. We can take advantage of this.
NOTE: This will be slightly hacky
You can write your own serialiser and have it store your values to retrieve them:
public static class MyGen extends GeneratorBase {
protected MyGen(int features, ObjectCodec codec) {
super(features, codec);
}
private Object currentObject = null;
@Override
public void flush() throws IOException {
// do nothing
}
@Override
protected void _releaseBuffers() {
// do nothing
}
@Override
protected void _verifyValueWrite(String typeMsg) throws IOException {
// do nothing
}
@Override
public void writeStartArray() throws IOException {
// do nothing
}
@Override
public void writeEndArray() throws IOException {
} // do nothing
@Override
public void writeStartObject() throws IOException {
// do nothing
}
@Override
public void writeEndObject() throws IOException {
// do nothing
}
@Override
public void writeFieldName(String name) throws IOException {
// do nothing
}
@Override
public void writeString(String text) throws IOException {
currentObject = text;
}
@Override
public void writeString(char[] text, int offset, int len) throws IOException {
currentObject = new String(text);
}
@Override
public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException {
currentObject = new String(text);
}
@Override
public void writeUTF8String(byte[] text, int offset, int length) throws IOException {
currentObject = new String(text);
}
@Override
public void writeRaw(String text) throws IOException {
currentObject = new String(text);
}
@Override
public void writeRaw(String text, int offset, int len) throws IOException {
currentObject = new String(text);
}
@Override
public void writeRaw(char[] text, int offset, int len) throws IOException {
currentObject = new String(text);
}
@Override
public void writeRaw(char c) throws IOException {
currentObject = new Character(c);
}
@Override
public void writeBinary(Base64Variant bv, byte[] data, int offset, int len) throws IOException {
currentObject = bv;
}
@Override
public void writeNumber(int v) throws IOException {
currentObject = new Integer(v);
}
@Override
public void writeNumber(long v) throws IOException {
currentObject = new Long(v);
}
@Override
public void writeNumber(BigInteger v) throws IOException {
currentObject = v;
}
@Override
public void writeNumber(double v) throws IOException {
currentObject = new Double(v);
}
@Override
public void writeNumber(float v) throws IOException {
currentObject = new Float(v);
}
@Override
public void writeNumber(BigDecimal v) throws IOException {
currentObject = v;
}
@Override
public void writeNumber(String encodedValue) throws IOException {
currentObject = encodedValue;
}
@Override
public void writeBoolean(boolean state) throws IOException {
currentObject = new Boolean(state);
}
@Override
public void writeNull() throws IOException {
currentObject = null;
}
}
Note, this is a prototype just-for-fun test and probably should be done with some more thought.
But with that, I can do:
public static void main(String[] args) throws JsonProcessingException, IOException {
MyGen gen = new MyGen(0, new ObjectMapper());
String test = "{ \"a\" : \"test\", \"b\" : 1, \"c\" : true, \"d\" : 2.5 }";
JsonNode tree = new ObjectMapper().readTree(test);
Impl instance = new DefaultSerializerProvider.Impl();
ObjectMapper m = new ObjectMapper();
for(JsonNode node : tree) {
node.serialize(gen, instance);
Object currentObject = gen.currentObject;
System.out.println(currentObject);
}
}
Which prints:
test
1
true
2.5
Hope that helps :)
Artur
Ps: The other answer is much shorter and better - but I thought this is cool too, so I'll post it anyway
Edit:
With regards to one of your comments. The generator will retrieve the original value and it will be up to you to preserve what that value is. You will have full control over that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With