3

This was a bit of an exercise in frustration made worse by the fact that I couldn't find any answers to this question. So I'm going to answer the question here.

What I find most difficult to understand was that JsonNode doesn't have any getName() or similar method which I was expecting given that JSON is a name:value data type. Though I realised after working out this solution that arrays aren't name:value.

See answer below.

HankCa
  • 9,129
  • 8
  • 62
  • 83

2 Answers2

13
package treeWalker;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;

public class TreeWalker
{
    public JsonNode convertJSONToNode(String json) throws JsonProcessingException, IOException
    {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNode = mapper.readTree(json);

        return jsonNode;
    }

    public void walkTree(JsonNode root)
    {
        walker(null, root);
    }

    private void walker(String nodename, JsonNode node)
    {
        String nameToPrint = nodename != null ? nodename : "must_be_root";
        System.out.println("walker - node name: " + nameToPrint);
        if (node.isObject())
        {
            Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();

            ArrayList<Map.Entry<String, JsonNode>> nodesList = Lists.newArrayList(iterator);
            System.out.println("Walk Tree - root:" + node + ", elements keys:" + nodesList);
            for (Map.Entry<String, JsonNode> nodEntry : nodesList)
            {
                String name = nodEntry.getKey();
                JsonNode newNode = nodEntry.getValue();

                // System.out.println("  entry - key: " + name + ", value:" + node);
                walker(name, newNode);
            }
        }
        else if (node.isArray())
        {
            Iterator<JsonNode> arrayItemsIterator = node.elements();
            ArrayList<JsonNode> arrayItemsList = Lists.newArrayList(arrayItemsIterator);
            for (JsonNode arrayNode : arrayItemsList)
            {
                walker("array item", arrayNode);
            }
        }
        else
        {
            if (node.isValueNode())
            {
                System.out.println("  valueNode: " + node.asText());
            }
            else
            {
                System.out.println("  node some other type");
            }
        }
    }
}

And a Unit test to exercise (with no asserts! Sorry).

package treeWalker;

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;


public class TreeWalkerTest
{
    TreeWalker treeWalker = new TreeWalker();

    private String getJSON()
    {
        String json = "{\"a\":\"val_a\",\"b\":\"val_b\",\"c\":[1,2,3]}";
        return json;
    }

    @Test
    public void testConvertJSONToNode() throws JsonProcessingException, IOException
    {
        String json = getJSON();

        JsonNode jNode = treeWalker.convertJSONToNode(json);

        System.out.println("jnode:" + jNode);

    }

    @Test
    public void testWalkTree() throws JsonProcessingException, IOException
    {
        JsonNode jNode = treeWalker.convertJSONToNode(getJSON());

        treeWalker.walkTree(jNode);
    }
}

Oh, and the build.gradle:

apply plugin: 'java'
apply plugin: 'eclipse'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.slf4j:slf4j-api:1.7.5'
    compile 'com.fasterxml.jackson.core:jackson-core:2.4.3'
    compile 'com.fasterxml.jackson.core:jackson-databind:2.4.3'
    compile 'com.fasterxml.jackson.core:jackson-annotations:2.4.3'
    compile 'com.google.guava:guava:18.0'

    testCompile "junit:junit:4.11"
}
HankCa
  • 9,129
  • 8
  • 62
  • 83
  • 2
    Thanks! I wish it wouldn't have used com.google.common.collect.Lists though. – medloh Apr 13 '16 at 20:19
  • 1
    This is already somewhat older, but it perfectly fits my needs for a piece of production code (and I couldn't be bothered to figure this out by myself, too much stress already). Just changed the "walker" method name to "walk", and modified the "walkTree" method signature to taken an additional Action parameter. Your original authorship is mentioned in the sources! – Jan van Oort Oct 10 '19 at 15:19
1

This is just an updated variation on HankCa's excellent answer. Under Java 8 and later, you can use Iterator.forEachRemaining() to loop through and you can externalize the consumer. Here's an updated version:

package treeWalker;

import java.io.IOException;
import java.util.function.BiConsumer;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TreeWalker
{
    public JsonNode convertJSONToNode(String json) throws JsonProcessingException, IOException
    {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNode = mapper.readTree(json);

        return jsonNode;
    }

    public void walkTree(JsonNode root, BiConsumer<String, String> consumer)
    {
        walker(null, root, consumer);
    }

    private void walker(String nodename, JsonNode node, BiConsumer<String, String> consumer)
    {
        String nameToPrint = nodename != null ? nodename : "must_be_root";
        if (node.isObject())
        {
            node.fields().forEachRemaining(e->walker(e.getKey(), e.getValue(), consumer));
        }
        else if (node.isArray())
        {
            node.elements().forEachRemaining(n->walker("array item of '" + nameToPrint + "'", n, consumer));
        }
        else
        {
            if (node.isValueNode())
            {
                consumer.accept(nameToPrint, node.asText());
            }
            else
            {
                throw new IllegalStateException("Node must be one of value, array or object.");
            }
        }
    }
}

Here's the unit test class (Now in Junit5):

package treeWalker;

import java.io.IOException;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;


public class TreeWalkerTest
{
    TreeWalker treeWalker = new TreeWalker();

    private String getJSON()
    {
        String json = "{\"a\":\"val_a\",\"b\":\"val_b\",\"c\":[1,2,3]}";
        return json;
    }

    @Test
    public void testConvertJSONToNode() throws JsonProcessingException, IOException
    {
        String json = getJSON();

        JsonNode jNode = treeWalker.convertJSONToNode(json);

        System.out.println("jnode:" + jNode);

    }

    @Test
    public void testWalkTree() throws JsonProcessingException, IOException
    {
        JsonNode jNode = treeWalker.convertJSONToNode(getJSON());

        treeWalker.walkTree(jNode, (a,b)->System.out.println("name='" + a + "', value='" + b + "'."));
    }
}
Rob McDougall
  • 328
  • 1
  • 10