Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson - generic getValue method

Tags:

java

json

jackson

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() + " |||");
        }
like image 361
peter.petrov Avatar asked Sep 15 '16 14:09

peter.petrov


People also ask

How does ObjectMapper readValue work?

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.

What is Jackson JsonNode?

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.

How to iterate through JsonNode in Java?

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.

How to define JsonNode in Java?

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.


2 Answers

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
like image 146
Radu Ionescu Avatar answered Oct 28 '22 20:10

Radu Ionescu


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.

like image 21
pandaadb Avatar answered Oct 28 '22 19:10

pandaadb