目标是从 JSON 字符串开始,解析它,然后递归处理每个对象属性或数组元素。如果该值是一个数组,并且该数组的每个元素都是一个包含“Name”属性的对象,则按 object.Name 对数组进行排序。递归很重要,因为我必须将其应用到的现实 JSON 具有需要排序的三层嵌套数组。
一旦数组按照确定的顺序排序,最后,它应该按字母顺序重新序列化整个属性。
这样做的目的是为了确保 JSON 的一致性,因此我可以在预期和实际之间进行字符串比较,并且如果事物以不同的顺序序列化,或者数组元素返回,则不会丢失它以不确定的顺序(目前就是这种情况)。
在伪代码中,它看起来像这样:
process_array( arr ) {
if ( arr.every( e => e.has("Name") ) ) {
arr.sort( byName )
}
arr.forEach( process )
}
process_object( obj ) {
for each key in obj {
process( obj[key] )
}
process( it ) {
if ( isArray( it ) {
process_arr( it )
} else {
process_object( it )
}
}
standardize( json ) {
generic_obj = parse( json )
process( generic_obj )
return serialize_with_sorted_keys( generic_obj )
}
为了使其具体化,请给出以下输入:
[
{
"Id": "1",
"Name": "foo",
"Children": [
{
"Name": "two",
"Value": 2
},
{
"Value": 1,
"Name": "one"
}
],
"Other": [ 1, 3, 2 ]
},
{
"Name": "bar",
"Id": "2",
"Children": [
{
"Name": "Banana",
"Value": 3
},
{
"Value": 4,
"Name": "Cherry"
},
{
"Apples": "are tasty",
"Name": "Apple",
"Value": 5
}
]
}
]
预期输出为:
[ // array elements are sorted by .Name
{ // object properties are sorted by key
"Children": [ // sorted by .Name
{
"Apples": "are tasty",
"Name": "Apple",
"Value": 5
},
{
"Name": "Banana",
"Value": 3
},
{ // properties sorted by name
"Name": "Cherry",
"Value": 4
}
],
"Id": "2",
"Name": "bar"
},
{
"Children": [
{
"Name": "two",
"Value": 2
},
{
"Name": "one",
"Value": 1
}
],
"Id": "1",
"Name": "foo",
"Other": [ 1, 2, 3 ] // NOTE: order has been changed!
}
]
如何在 Java 中以通用方式执行此操作?
这是我的尝试(不一定是最佳的,但可以满足我的需求)。通过
SortingNodeFactory
对对象属性进行排序来自 https://cowtowncoder.medium.com/jackson-tips-sorting-json-using-jsonnode-ce4476e37aee。该类中有一个 Jackson 依赖项,它通常是基本 Spring Boot 设置的一部分。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.*;
import java.util.*;
public class JsonSorter {
private final ObjectMapper objectMapper = new ObjectMapper();
Comparator<JsonNode> nodeComparator = Comparator.comparing(this::toString);
public String run(String json) throws JsonProcessingException {
ObjectMapper sortingMapper = JsonMapper.builder()
.nodeFactory(new SortingNodeFactory())
.build();
JsonNode jsonNode = sortingMapper.readTree(json);
sortArrays(jsonNode);
return sortingMapper.writeValueAsString(jsonNode);
}
// see https://cowtowncoder.medium.com/jackson-tips-sorting-json-using-jsonnode-ce4476e37aee
static class SortingNodeFactory extends JsonNodeFactory {
@Override
public ObjectNode objectNode() {
return new ObjectNode(this, new TreeMap<>());
}
}
private String toString(JsonNode node) {
try {
return objectMapper.writeValueAsString(node);
} catch (Exception ex) {
return "";
}
}
private void sortArrays(JsonNode node) {
if (node.isArray()) {
for (JsonNode child : node) {
sortArrays(child);
}
// Sort elements in arrayNode
var arrayNode = (ArrayNode) node;
var iter = arrayNode.elements();
var sortedElemsCopy = new ArrayList<JsonNode>(arrayNode.size());
while(iter.hasNext()) {
var n = iter.next();
sortedElemsCopy.add(n);
}
sortedElemsCopy.sort(nodeComparator);
arrayNode.removeAll();
arrayNode.addAll(sortedElemsCopy);
} else if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node;
Iterator<Map.Entry<String, JsonNode>> iter = objectNode.fields();
while (iter.hasNext()) {
Map.Entry<String, JsonNode> entry = iter.next();
sortArrays(entry.getValue());
}
}
}
}
运行代码:
String jsonContent = ...
var normalizedJsonContent = new JsonSorter().run(jsonContent);
希望有帮助。