4

I have an enhanced question regarding Flatten a JSON string to Map using Gson or Jackson.

My scenario included duplicated keys, so the solution in the above question will cause some duplicated keys overwritten. So I am thinking to construct keys by combining each level's key together.

So how to achieve that?

For example:

{
    "id" : "123",
    "name" : "Tom",
    "class" : {
        "subject" : "Math",
        "teacher" : "Jack"
    }
}

I want to get the Map:

"id" : "123",
"name" : "Tom",
"class.subject" : "Math",
"class.teacher" : "Jack"

************************Update Solution*************************************

Based on @Manos Nikolaidis's answer, I am able to achieve the following solution by considering ArrayNode.

public void processJsonString(String jsonString) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    ArrayNode arrayNode = (ArrayNode) mapper.readTree(jsonString);
    processArrayNode(arrayNode);
}

private void processObjectNode(JsonNode jsonNode) {
    Map<String, String> result = new HashMap<>();
    Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields();
    iterator.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
}

private void processArrayNode(ArrayNode arrayNode) {
    for (JsonNode jsonNode : arrayNode) {
        processObjectNode(jsonNode);
    }
}


private void mapAppender(Map<String, String> result, Map.Entry<String, JsonNode> node, List<String> names) {
    names.add(node.getKey());
    if (node.getValue().isTextual()) {
        String name = names.stream().collect(Collectors.joining("."));
        result.put(name, node.getValue().asText());
    } else if (node.getValue().isArray()) {
        processArrayNode((ArrayNode) node.getValue());
    } else if (node.getValue().isNull()) {
        String name = names.stream().collect(Collectors.joining("."));
        result.put(name, null);
    } else {
        node.getValue().fields()
                        .forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
    }
}
Lazysheep.wang
  • 129
  • 1
  • 2
  • 12
  • I think you might have to simply recursively (or iteratively) do the transformation yourself. – chrylis -cautiouslyoptimistic- Jul 10 '17 at 18:52
  • Actually I found another question which was able to iterate json string, and print the value correctly. But how can I add/save the key? I was not able to figure it out. Would you please help? https://stackoverflow.com/questions/22111857/parse-recursively-unknown-json-input-structure-in-java – Lazysheep.wang Jul 10 '17 at 18:54

2 Answers2

1

You can get the JSON as JsonNode and go through all fields recursively and add key and value field to a Map. When a value is an object instead of string you can add the field name to List to be joined with periods when a string is finally encountered. First create (for readability) a separate method that add Json fields to a Map:

void mapAppender(Map<String, String> result, Entry<String, JsonNode> node, List<String> names) {
    names.add(node.getKey());
    if (node.getValue().isTextual()) {
        String name = names.stream().collect(joining("."));
        result.put(name, node.getValue().asText());
    } else {
        node.getValue().fields()
            .forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
    }
}

and use it like this:

ObjectMapper mapper = new ObjectMapper();
Map<String, String> result = new HashMap<>();
mapper.readTree(json).fields()
    .forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));

Where fields() returns an Iterator. Beware of StackOverflowErrors and perhaps low performance for deeply nested JSON.

Manos Nikolaidis
  • 21,608
  • 12
  • 74
  • 82
1

I resolved this using below simple code, Only think is need to download jettison and flattener.JsonFlattener library

import java.util.Map;
import org.codehaus.jettison.json.JSONObject;
import com.github.wnameless.json.flattener.JsonFlattener;

public class test {

    public static void main(String[] args) {
        String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
        JSONObject jsonObject = new JSONObject();
        String flattenedJson = JsonFlattener.flatten(jsonString);
        Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(jsonString);
        System.out.println(flattenedJsonMap);
    }
}

Reference link : https://github.com/wnameless/json-flattener

Noyal
  • 223
  • 2
  • 7