如何解析包含数组的JSON数组 Java

huangapple go评论79阅读模式
英文:

How to Parse Json containing Array of Arrays Java

问题

我有一个包含多个数组的JSON,现在我需要解析该JSON并计算元素数量,在达到一定限制后,我需要将其放入结果JSON中。我已经能够解析到一级并计算元素数量。如何解析多级并以相同格式获取对象:

以下是我尝试用于解析一级并计数元素数量的示例代码:

private void handleJson(Object jsonObj, CountObject c, JSONObject jsonObj) {
    Map<String, Object> map = new HashMap<>();

    if (jsonObj instanceof JSONObject) {

        parseJson(inputJSON, c, map, jsonObj);

    }

}

private void parseJson(Object inputObj, CountObject c, Map<String, Object> map, JSONObject jsonObj) {
    JSONObject nodeJson = (JSONObject) inputJSON;
    Iterator<String> keyIter = nodeJson.keySet().iterator();

    while (keyIter.hasNext()) {
        String key = keyIter.next();
        Object value = nodeJson.get(key);

        if (value instanceof JSONObject) {
            int offSet = c.getOffSet();
            if (c.getLimit() == c.getOffSet()) {
                break;
            }
            keyIter.remove();
            map.put(key, value);
            c.setOffSet(++offSet);;
        } else {
            handleJSONArray(value, k, map, key);
        }
    }
    for (Entry<String, Object> entry : map.entrySet()) {
        jsonObj.put(entry.getKey(), entry.getValue());
    }
}

private void handleJSONArray(Object inputJSON, CountObject c, Map<String, Object> map, String key) {
    JSONArray nodeJsonArr = (JSONArray) inputJSON;
    int offSet = c.getOffSet();
    List<Object> ll = new ArrayList<>();
    for (int i = 0; i < nodeJsonArr.length(); i++) {
        Object value = nodeJsonArr.get(i);
        if (value instanceof JSONArray) {
            handleJSONArray(value, c, map, key2);
        } else {

            if (k.getLimit() == k.getOffSet()) {
                break;
            }
            ll.add(value);
            ++offSet;
        }
    }
    map.put(key2, ll);
    c.setOffSet(offSet);
}

以下是我的JSON示例:

{
    "emails": [
        {
            "emails": [
                {
                    "email": {
                        "id": "ac9e95cf-3338-4094-b465-e0e1deca23c4",
                        "value": "hello@gmail.com"
                    }
                }
            ]
        },
        {
            "email": {
                "id": "b61ffb48-ffc7-4ae6-81a2-78b632892fda",
                "value": "hello1@gmail.com"
            }
        }
    ],
    "lastName": {
        "id": "ffe19ece-819b-4680-8e0b-8566b34c973d",
        "value": "FirstName"
    },
    "firstName": {
        "id": "4ed234f4-f679-40f3-b76b-41d9fdef7390",
        "value": "LastName"
    }
}

CountObject是一个POJO,其中包含偏移量和限制变量。如果我传递限制为3,我应该只获取前3个元素,并且格式与上面的JSON示例相同。

英文:

I have a Json which contains array of Arrays, now I need to parse that Json and count the elements, and after reaching certain limit I need to put it into result Json. I was able to parse till one level and count the elements. How can I parse multiple levels and get the object in same format:

here is the sample code I tried for parsing one level with counting no.of elements:

private void handleJson(Object jsonObj, CountObject c, JSONObject jsonObj) {
Map&lt;String, Object&gt; map= new HashMap&lt;&gt;();
if (jsonObj instanceof JSONObject) {
parseJson(inputJSON,c, map, jsonObj);
} 
}
}
private void parseJson(Object inputObj, CountObject c, Map&lt;String, Object&gt; map, JSONObject jsonObj) {
JSONObject nodeJson = (JSONObject) inputJSON;
Iterator&lt;String&gt; keyIter = nodeJson.keySet().iterator();
while (keyIter.hasNext()) {
String key = keyIter.next();
Object value = nodeJson.get(key);
if (value instanceof JSONObject) {
int offSet = c.getOffSet();
if(c.getLimit() == c.getOffSet()) {
break;
}
keyIter.remove(); 
map.put(key, value); 
c.setOffSet(++offSet);;
} else {
handleJSONArray(value,k, map, key);
}
}
for (Entry&lt;String, Object&gt; entry : map.entrySet()) {
jsonObj.put(entry.getKey(), entry.getValue());
}
}
private void handleJSONArray(Object inputJSON, CountObject c, Map&lt;String, Object&gt; map, String key) {
JSONArray nodeJsonArr = (JSONArray) inputJSON;
int offSet = c.getOffSet();
List&lt;Object&gt; ll = new ArrayList&lt;&gt;();
for (int i = 0; i &lt; nodeJsonArr.length(); i++) {
Object value = nodeJsonArr.get(i);
if (value instanceof JSONArray) {
handleJSONArray(value, c, map, key2);
} else {
if (k.getLimit() == k.getOffSet()) {
break;
}
ll.add(value);
++offSet;
}
}
map.put(key2, ll);
c.setOffSet(offSet);
}

and here is my Json :

{
&quot;emails&quot;: [
{
&quot;emails&quot;: [
{
&quot;email&quot;: {
&quot;id&quot;: &quot;ac9e95cf-3338-4094-b465-e0e1deca23c4&quot;,
&quot;value&quot;: &quot;hello@gmail.com&quot;
}
}
]
},
{
&quot;email&quot;: {
&quot;id&quot;: &quot;b61ffb48-ffc7-4ae6-81a2-78b632892fda&quot;,
&quot;value&quot;: &quot;hello1@gmail.com&quot;
}
}
],
&quot;lastName&quot;: {
&quot;id&quot;: &quot;ffe19ece-819b-4680-8e0b-8566b34c973d&quot;,
&quot;value&quot;: &quot;FirstName&quot;
},
&quot;firstName&quot;: {
&quot;id&quot;: &quot;4ed234f4-f679-40f3-b76b-41d9fdef7390&quot;,
&quot;value&quot;: &quot;LastName&quot;
}
}

And count Object is a Pojo which has offset and Limit variables , If I pass limit as 3 I should fetch only first 3 elements with same json format something like below :

{
&quot;emails&quot;: [
{
&quot;emails&quot;: [
{
&quot;email&quot;: {
&quot;id&quot;: &quot;ac9e95cf-3338-4094-b465-e0e1deca23c4&quot;,
&quot;value&quot;: &quot;hello@gmail.com&quot;
}
}
]
},
{
&quot;email&quot;: {
&quot;id&quot;: &quot;b61ffb48-ffc7-4ae6-81a2-78b632892fda&quot;,
&quot;value&quot;: &quot;hello1@gmail.com&quot;
}
}
],
&quot;lastName&quot;: {
&quot;id&quot;: &quot;ffe19ece-819b-4680-8e0b-8566b34c973d&quot;,
&quot;value&quot;: &quot;FirstName&quot;
}

Here I gave one of the sample JSON file, and Json can contain any no.of inner Array of elements, logic should be able to parse any type of Json.
Here I should do the pagination as well for Json elements, means if I pass offSet and limit and I should fetch the elements accordingly. In the above example CountObject contains limit and offSet based on that it should fetch the elements.
TO give more explanation If I pass offSet as 10 and limit a 10 I should fetch the elements in from 10th element to 20th element and so on.

答案1

得分: 1

以下是使用Jackson(我使用的版本是2.11.1)的一种方法。

一个"项目"在这里被定义为源JSON中的一个id/value对之一,例如:

{
  "id": "b61ffb48-ffc7-4ae6-81a2-78b632892fda",
  "value": "hello1@gmail.com"
}

我将任务分为2个部分:

  1. 当达到所需限制时,通过删除后续项目来截断数据。

  2. 清理任何产生的空对象或数组。

以下是我的输入测试数据(基于问题中提供的数据):

private static final String JSON = "{
  \"emails\": [{
    \"emails\": [{
      \"email\": {
        \"id\": \"ac9e95cf-3338-4094-b465-e0e1deca23c4\",
        \"value\": \"hello@gmail.com\"
      }
    }]
  }, {
    \"email\": {
      \"id\": \"b61ffb48-ffc7-4ae6-81a2-78b632892fda\",
      \"value\": \"hello1@gmail.com\"
    }
  }],
  \"lastName\": {
    \"id\": \"ffe19ece-819b-4680-8e0b-8566b34c973d\",
    \"value\": \"LastName\"
  },
  \"firstName\": {
    \"id\": \"4ed234f4-f679-40f3-b76b-41d9fdef7390\",
    \"value\": \"FirstName\"
  }
}";

代码:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Iterator;

public class JsonReducer {

    // 获取前n个"id/value"项目:
    private final int limit = 2;
    // 跟踪接近截断限制的次数:
    private int counter = 0;

    public void doParsing() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode json = mapper.readValue(JSON, JsonNode.class);
        
        // 仅用于此演示的格式化输入JSON:
        System.out.println(json.toPrettyString());

        // 原始数据的副本 - 我们在清理时将使用它:
        JsonNode prevJson = json.deepCopy();
        // 从JSON中删除不需要的项目
        json = reduce(json);

        // 清理由于删除而产生的空节点:
        while (!json.equals(prevJson)) {
            prevJson = json.deepCopy();
            json = stripEmpty(json);
        }

        System.out.println("---------------------------------");
        System.out.println(json.toPrettyString());
    }

    private JsonNode reduce(JsonNode json) {
        for (JsonNode node : json) {
            if (node.isObject()) {
                counter++;
                if (counter > limit) {
                    ((ObjectNode) node).removeAll();
                } else {
                    reduce(node);
                }
            } else if (node.isArray()) {
                ArrayNode arrayNode = (ArrayNode) node;
                arrayNode.forEach((item) -> {
                    // 假设每个项目都是JSON对象 - 没有数组的数组:
                    ObjectNode objectNode = (ObjectNode) item;
                    reduce(objectNode);
                });
            }
        }
        return json;
    }

    private JsonNode stripEmpty(JsonNode json) {
        Iterator<JsonNode> it = json.iterator();
        while (it.hasNext()) {
            JsonNode child = it.next();
            if (child.isContainerNode() && child.isEmpty()) {
                it.remove(); // 删除空数组[]和对象{}
            } else {
                stripEmpty(child);
            }
        }
        return json;
    }

    private static final String JSON = ... // 如上所示。

}

请注意,这个方法假设没有直接嵌套在其他数组中的数组 - 所以没有这种情况:[ [ {...} ] ]。换句话说,这不是一个100%通用的解析器,但与问题中的示例数据一致,有一些限制。

考虑使用POJO

这个解决方案没有定义任何POJO Java对象,用来加载数据 - 但通常通过执行以下操作可以更容易地获得所需的内容:

  • 将数据加载(反序列化)到一个或多个POJO中。
  • 从POJO中删除不需要的数据。
  • 将剩余的数据序列化回JSON。

如果示例比问题中提供的更复杂,我认为我会更倾向于执行这个操作,而不仅仅是操作JsonNode数据。

更新

鉴于问题的更改,我认为我可以建议的最佳方法是将每个"项目"(请参阅上面的定义)解析为一个POJO,该POJO简单地包含3个字段:

String attribute;
String id;
String value;

执行此操作的代码如下:

private void traverse(JsonNode json) {
    Iterator<Map.Entry<String, JsonNode>> it = json.fields();
    while (it.hasNext()) {
        Map.Entry<String, JsonNode> entry = it.next();
        String name = entry.getKey();
        JsonNode node = entry.getValue();

        if (node.isArray()) {
            ArrayNode arrayNode = (ArrayNode) node;
            arrayNode.forEach((item) -> {
                // 假设每个项目都是JSON对象 - 没有数组的数组:
                ObjectNode objectNode = (ObjectNode) item;
                traverse(objectNode);
            });
        } else {
            String id = node.get("id").asText();
            String value = node.get("value").asText();
            
            System.out.println("attr : " + name);
            System.out.println("id   : " + id);
            System.out.println("value: " + value);
            System.out.println("---");
        }
    }
}

println()语句之后,您将创建一个POJO的新实例并将其添加到ArrayList中。

现在,您有一个包含所有数据的标准列表 - 您可以根据用户界面的需要访问项目1 - 100、101 - 200...等等。

当然,您需要将原始POJO数据转换回UI需要/期望的任何格式。

使用问题中的示例JSON,上述方法打印如下内容:

attr : email
id   : ac9e95cf-3338-4094-b465-e0e1deca23c4
value:

<details>
<summary>英文:</summary>

Here is an approach using Jackson (I used version 2.11.1).

An &quot;**item**&quot; here is defined as one of the id/value pairs in the source JSON - for example:

{
"id": "b61ffb48-ffc7-4ae6-81a2-78b632892fda",
"value": "hello1@gmail.com"
}

I split the task into 2 parts:
1) Cut off the data when the required limit is reached, by deleting subsequent items.
2) Clean up any resulting empty objects or arrays.
Here is my input test data (based on the data provided in the question):
private static final String JSON = &quot;{\n&quot;
+ &quot;	\&quot;emails\&quot;: [{\n&quot;
+ &quot;			\&quot;emails\&quot;: [{\n&quot;
+ &quot;				\&quot;email\&quot;: {\n&quot;
+ &quot;					\&quot;id\&quot;: \&quot;ac9e95cf-3338-4094-b465-e0e1deca23c4\&quot;,\n&quot;
+ &quot;					\&quot;value\&quot;: \&quot;hello@gmail.com\&quot;\n&quot;
+ &quot;				}\n&quot;
+ &quot;			}]\n&quot;
+ &quot;		},\n&quot;
+ &quot;		{\n&quot;
+ &quot;			\&quot;email\&quot;: {\n&quot;
+ &quot;				\&quot;id\&quot;: \&quot;b61ffb48-ffc7-4ae6-81a2-78b632892fda\&quot;,\n&quot;
+ &quot;				\&quot;value\&quot;: \&quot;hello1@gmail.com\&quot;\n&quot;
+ &quot;			}\n&quot;
+ &quot;		}\n&quot;
+ &quot;	],\n&quot;
+ &quot;	\&quot;lastName\&quot;: {\n&quot;
+ &quot;		\&quot;id\&quot;: \&quot;ffe19ece-819b-4680-8e0b-8566b34c973d\&quot;,\n&quot;
+ &quot;		\&quot;value\&quot;: \&quot;LastName\&quot;\n&quot;
+ &quot;	},\n&quot;
+ &quot;	\&quot;firstName\&quot;: {\n&quot;
+ &quot;		\&quot;id\&quot;: \&quot;4ed234f4-f679-40f3-b76b-41d9fdef7390\&quot;,\n&quot;
+ &quot;		\&quot;value\&quot;: \&quot;FirstName\&quot;\n&quot;
+ &quot;	}\n&quot;
+ &quot;}&quot;;
The code:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Iterator;

public class JsonReducer {

// get the first n &quot;id/value&quot; items:
private final int limit = 2;
// tracks how close we are to the cutoff limit:
private int counter = 0;
public void doParsing() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readValue(JSON, JsonNode.class);
// show the input JSON formatted, just for this demo:
System.out.println(json.toPrettyString());
// a copy of the original - we will use this when cleaning up:
JsonNode prevJson = json.deepCopy();
// remove unwanted items from the JSON
json = reduce(json);
// clean up empty nodes resulting from removals:
while (!json.equals(prevJson)) {
prevJson = json.deepCopy();
json = stripEmpty(json);
}
System.out.println(&quot;---------------------------------&quot;);
System.out.println(json.toPrettyString());
}
private JsonNode reduce(JsonNode json) {
for (JsonNode node : json) {
if (node.isObject()) {
counter++;
//System.out.println(&quot;obj &quot; + counter + &quot; - &quot; + node.toString());
if (counter &gt; limit) {
((ObjectNode) node).removeAll();
} else {
reduce(node);
}
} else if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node;
//System.out.println(&quot;array - &quot; + arrayNode.toString());
arrayNode.forEach((item) -&gt; {
// assume each item is a JSON object - no arrays of arrays:
ObjectNode objectNode = (ObjectNode) item;
reduce(objectNode);
});
} //else if (node.isTextual()) {
//System.out.println(&quot;text  - &quot; + node.asText());
//}
}
return json;
}
private JsonNode stripEmpty(JsonNode json) {
Iterator&lt;JsonNode&gt; it = json.iterator();
while (it.hasNext()) {
JsonNode child = it.next();
if (child.isContainerNode() &amp;&amp; child.isEmpty()) {
it.remove(); // remove empty arrays [], and objects {}
} else {
stripEmpty(child);
}
}
return json;
}
private static final String JSON = ... // as shown above.

}

The `reduce()` method recursively iterates through the JSON, keeping track of the number of items collected - and then deletes any in excess of the required number.
However, this can leave empty `[]` arrays or `{}` objects in the JSON, so the `stripEmpty()` method handles that.
Because we are iterating sequentially through the JSON from top to bottom and from outer to inner, it&#39;s possible that we may need more than one pass of the `stripEmpty()` method. There may be a more efficient approach, which only needs one pass, but this is approach is at least straightforward.
Examples of the results:
For limit = 2:

{
"emails" : [ {
"emails" : [ {
"email" : {
"id" : "ac9e95cf-3338-4094-b465-e0e1deca23c4",
"value" : "hello@gmail.com"
}
} ]
}, {
"email" : {
"id" : "b61ffb48-ffc7-4ae6-81a2-78b632892fda",
"value" : "hello1@gmail.com"
}
} ]
}

For limit = 1:

{
"emails" : [ {
"emails" : [ {
"email" : {
"id" : "ac9e95cf-3338-4094-b465-e0e1deca23c4",
"value" : "hello@gmail.com"
}
} ]
} ]
}

For limit = 0:

{ }


**Additional Points:**
*Not Generic*
The approach assumes there are never any arrays nested directly inside other arrays - so none of this: `[ [ {...} ] ]`. In other words, this is not a 100% generic parser, but does have some limitations in line with the sample data in the question.
*Consider using POJOs*
This solution does not define any POJO java objects into which the data is loaded - but it can often be easier to get what you want by doing that:
- load (deserialize) the data into one or more POJOs.
- remove unwanted data from the POJOs.
- serialize the remaining data back to JSON.
If the example were any more complicated than the one in the question, I think I would favor doing this instead of manipulating only `JsonNode` data.
Update
---
Given the changes to the question, I think the best approach I can suggest is to parse each &quot;**item**&quot; (see definition above) into a POJO which would simply contain 3 fields:

String attribute;
String id;
String value;

The code to do this is as follows:
private void traverse(JsonNode json) {
Iterator&lt;Map.Entry&lt;String, JsonNode&gt;&gt; it = json.fields();
while (it.hasNext()) {
Map.Entry&lt;String, JsonNode&gt; entry = it.next();
String name = entry.getKey();
JsonNode node = entry.getValue();
if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node;
arrayNode.forEach((item) -&gt; {
// assume each item is a JSON object - no arrays of arrays:
ObjectNode objectNode = (ObjectNode) item;
traverse(objectNode);
});
} else {
String id = node.get(&quot;id&quot;).asText();
String value = node.get(&quot;value&quot;).asText();
System.out.println(&quot;attr : &quot; + name);
System.out.println(&quot;id   : &quot; + id);
System.out.println(&quot;value: &quot; + value);
System.out.println(&quot;---&quot;);
}
}
}
Instead of the `println()` statements, you would create a new instance of the POJO and add it to an `ArrayList`.
Now you have a standard list containing all your data - and you can access items 1 - 100, then 101 - 200... and so on, as needed for the user interface.
**You would need to convert that raw POJO data back to whatever format the UI needs/expects, of course.**
Using the example JSON from the question, the above approach prints this:

attr : email
id : ac9e95cf-3338-4094-b465-e0e1deca23c4
value: hello@gmail.com

attr : email
id : b61ffb48-ffc7-4ae6-81a2-78b632892fda
value: hello1@gmail.com

attr : lastName
id : ffe19ece-819b-4680-8e0b-8566b34c973d
value: LastName

attr : firstName
id : 4ed234f4-f679-40f3-b76b-41d9fdef7390
value: FirstName


</details>

huangapple
  • 本文由 发表于 2020年8月11日 23:19:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/63361278.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定