如何在 groovy/java 脚本中比较两个 json 并获得原始输入 json 格式的差异

How to compare two json and get difference as original input json format in groovy/java script

提问人: 提问时间:9/14/2023 更新时间:9/14/2023 访问量:21

问:

我必须比较两个 json 对象,并且需要以与输入 json 相同的格式获取它们的差异。 例如,我输入了 json 1,如下所示:

{
    "entities": [
        {
            "L1_Entity_Id": 7004,
            "L1_Entity_Nm": "AcademicAdministration",
            "visibility": false,
            "L2_Entities": [
                {
                    "L2_Entity_Id": 8003,
                    "L2_Entity_Nm": "DesktopSoftware",
                    "visibility": true,
                    "primary_triage": "EAAS",
                    "markham_triage": "MK_Team1",
                    "Faculty_Support": "Faculty1,Faculty2,Faculty4",
                    "hasReference": true
                }
            ],
            "hasReference": true
        },
        {
            "L1_Entity_Id": 7005,
            "L1_Entity_Nm": "Websites",
            "visibility": false,
            "L2_Entities": [
                {
                    "L2_Entity_Id": 8013,
                    "L2_Entity_Nm": "CampusMaps",
                    "visibility": false,
                    "Faculty_Support": "Faculty3,Faculty2",
                    "hasReference": false
                },
                {
                    "L2_Entity_Id": 8014,
                    "L2_Entity_Nm": "WebGeneral",
                    "visibility": false,
                    "Faculty_Support": "Faculty4",
                    "hasReference": false
                }
            ],
            "hasReference": false
        }
    ]
}

我的输入json 2如下:

{
    "entities": [
        {
            "L1_Entity_Id": 7004,
            "L1_Entity_Nm": "AcademicAdministration",
            "visibility": false,
            "L2_Entities": [
                {
                    "L2_Entity_Id": 8003,
                    "L2_Entity_Nm": "DesktopSoftware",
                    "visibility": true,
                    "primary_triage": "EAAS",
                    "markham_triage": "MK_Team1",
                    "Faculty_Support": "Faculty1,Faculty2,Faculty4",
                    "hasReference": true
                }
            ],
            "hasReference": true
        },
        {
            "L1_Entity_Id": 7005,
            "L1_Entity_Nm": "Websites",
            "visibility": false,
            "L2_Entities": [
                {
                    "L2_Entity_Id": 8013,
                    "L2_Entity_Nm": "CampusMaps2",
                    "visibility": false,
                    "Faculty_Support": "Faculty3,Faculty2",
                    "hasReference": false
                },
                {
                    "L2_Entity_Id": 8014,
                    "L2_Entity_Nm": "WebGeneral",
                    "visibility": false,
                    "Faculty_Support": "Faculty4",
                    "hasReference": false
                }
            ],
            "hasReference": false
        }
    ]
}

我刚刚将L2_Entity_Nm从 CampusMaps 更改为 CampusMaps2。

现在脚本的预期输出如下: 即保持与输入json相同的格式,并在子数组中添加操作字段为“operation”: “UPDATED”L2_Entities。

{
    "entities": [
        {
            "L1_Entity_Id": 7005,
            "L1_Entity_Nm": "Websites",
            "visibility": false,
            "L2_Entities": [
                {
                    "L2_Entity_Id": 8013,
                    "L2_Entity_Nm": "CampusMaps2",
                    "visibility": false,
                    "Faculty_Support": "Faculty3,Faculty2",
                    "hasReference": false
                    "operation": "UPDATED"
                }
            ],
            "hasReference": false
        }
    ]
}

我从这里参考了 - https://smyachenkov.com/posts/json-difference-in-java/ 并创建了以下脚本:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ObjectMapper; 
import java.util.*;




System.out.println("@@ Initialization START----------------------------------> ");



var from = toJson(
"{\"entities\":[{\"L1_Entity_Id\":7004,\"L1_Entity_Nm\":\"AcademicAdministration\",\"visibility\":false,\"L2_Entities\":[{\"L2_Entity_Id\":8003,\"L2_Entity_Nm\":\"DesktopSoftware\",\"visibility\":true,\"primary_triage\":\"EAAS\",\"markham_triage\":\"MK_Team1\",\"Faculty_Support\":\"Faculty1,Faculty2,Faculty4\",\"hasReference\":true}],\"hasReference\":true},{\"L1_Entity_Id\":7005,\"L1_Entity_Nm\":\"Websites\",\"visibility\":false,\"L2_Entities\":[{\"L2_Entity_Id\":8013,\"L2_Entity_Nm\":\"CampusMaps\",\"visibility\":false,\"Faculty_Support\":\"Faculty3,Faculty2\",\"hasReference\":false},{\"L2_Entity_Id\":8014,\"L2_Entity_Nm\":\"WebGeneral\",\"visibility\":false,\"Faculty_Support\":\"Faculty4\",\"hasReference\":false}],\"hasReference\":false}]}" );

var to = toJson(
"{\"entities\":[{\"L1_Entity_Id\":7004,\"L1_Entity_Nm\":\"AcademicAdministration2\",\"visibility\":false,\"L2_Entities\":[{\"L2_Entity_Id\":8003,\"L2_Entity_Nm\":\"DesktopSoftware\",\"visibility\":true,\"primary_triage\":\"EAAS\",\"markham_triage\":\"MK_Team1\",\"Faculty_Support\":\"Faculty1,Faculty2,Faculty4\",\"hasReference\":true}],\"hasReference\":true},{\"L1_Entity_Id\":7005,\"L1_Entity_Nm\":\"Websites\",\"visibility\":false,\"L2_Entities\":[{\"L2_Entity_Id\":8013,\"L2_Entity_Nm\":\"CampusMaps\",\"visibility\":false,\"Faculty_Support\":\"Faculty3,Faculty2\",\"hasReference\":false},{\"L2_Entity_Id\":8014,\"L2_Entity_Nm\":\"WebGeneral\",\"visibility\":false,\"Faculty_Support\":\"Faculty4\",\"hasReference\":false}],\"hasReference\":false}]}"    );




var result = new JsonDiff().diff(from, to);


System.out.println("@@@ result  = "+result);


private LinkedHashMap<String, Object> toJson(String json) {
    try {
      return new ObjectMapper().readValue(json, LinkedHashMap.class);
    } catch (Exception e) {
      return null;
    }
  }

/*************************************************************************** 
                            Supporting Classes
****************************************************************************/

public enum Operation {
  ADDED, REMOVED, UPDATED
}


public class Difference {

  private Object value;

  private String path;

  private Operation operation;

  public Difference(Object value, String path, Operation operation) {
    this.value = value;
    this.path = path;
    this.operation = operation;
  }



  public Object getValue() {
    return value;
  }

  public String getPath() {
    return path;
  }

  public Operation getOperation() {
    return operation;
  }

  @Override
  public String toString() {
    return "Difference{" +
           "value=" + value +
           ", path='" + path + '\'' +
           ", operation=" + operation +
           '}';
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Difference that = (Difference) o;
    return Objects.equals(value, that.value) &&
           Objects.equals(path, that.path) &&
           operation == that.operation;
  }

  @Override
  public int hashCode() {
    return Objects.hash(value, path, operation);
  }
}


public class JsonDiff {
    
    ObjectMapper mapper = new ObjectMapper();
    ObjectNode output = mapper.createObjectNode();
    ArrayNode changeEntities = output.putArray("changeEntities");
   
  private static final Set<Class<?>> JSON_PRIMITIVES = Set.of(
      Integer.class,
      Long.class,
      Double.class,
      String.class,
      Boolean.class
  );

  public List<Difference> diff(Map<String, Object> from,
                               Map<String, Object> to) {
    return diff(from, to, "/");
  }

  public List<Difference> diff(Map<String, Object> from,
                               Map<String, Object> to,
                               String path) {
    
   
    if (from == to) {
      return List.of();
    }
    List<Difference> differences = new ArrayList<>();
    Set<String> keys = new HashSet<>();
    println("from keys=" + from.keySet());
    println("to keys=" + to.keySet());
    keys.addAll(from.keySet());
    keys.addAll(to.keySet());
   
    keys.forEach(key -> {
      // key is removed
      if (!to.containsKey(key) && from.containsKey(key)) {
        differences.add(new Difference(from.get(key), path + key, Operation.REMOVED));
      // new key is added
      } else if (to.containsKey(key) && !from.containsKey(key)) {
        differences.add(new Difference(to.get(key), path + key, Operation.ADDED));
      // existing key is modified
      } else {
        println("existing key is modified");  
        differences.addAll(compare(from.get(key), to.get(key), path + key + "/"));
      }
    });
    
    System.out.println("@@@ output  = "+output);
    return differences;
  }

  private List<Difference> compare(Object from, Object to, String path) {
    println("path="+ path)  
    println("from="+ from)  
    println("to="+ to)  
    var differences = new ArrayList<Difference>();
    var fromClass = from.getClass();
    var toClass = to.getClass();
    if (oneIsPrimitive(fromClass, toClass)) {
            
      if (!from.equals(to)) {
        changeEntities.add(to);  
        println("oneIsPrimitive To="+to);
        differences.add(new Difference(to, path, Operation.UPDATED));
      }
    } else if (bothAreObjects(from, to)) {
      println("both are object")    
      differences.addAll(diff((Map<String, Object>) from, (Map<String, Object>) to, path));
    } else if (bothAreArrays(fromClass, toClass)) {
      println("bothAreArrays")    
      var fromArray = (ArrayList<Object>) from;
      var toArray = (ArrayList<Object>) to;
      var arrayDiffs = new ArrayList<Difference>();
      for (int i = 0; i < Math.min(fromArray.size(), toArray.size()); i++) {
        println("changed json=" + toArray.get(i))  
        arrayDiffs.addAll(compare(fromArray.get(i), toArray.get(i), path + i + "/"));
      }
      // add new to fromArray
      if (toArray.size() > fromArray.size()) {
        for (int i = fromArray.size(); i < toArray.size(); i++) {
          arrayDiffs.add(new Difference(toArray.get(i), path + i, Operation.ADDED));
        }
      }
      // remove extra from fromArray
      if (toArray.size() < fromArray.size()) {
        for (int i = toArray.size(); i < fromArray.size(); i++) {
          arrayDiffs.add(new Difference(fromArray.get(i), path + i, Operation.REMOVED));
        }
      }
      differences.addAll(arrayDiffs);
    }
    return differences;
  }

  private boolean oneIsPrimitive(Class<?> from, Class<?> to) {
    return JSON_PRIMITIVES.contains(to) || JSON_PRIMITIVES.contains(from);
  }

  private boolean bothAreObjects(Object from, Object to) {
    return from instanceof Map && to instanceof Map;
  }

  private boolean bothAreArrays(Class<?> from, Class<?> to) {
    return from == ArrayList.class && to == ArrayList.class;
  }


}


System.out.println("@@Initialization END----------------------------------- ");

which you can run online at - https://www.jdoodle.com/execute-groovy-online/

Once executed you will see it gives result as [Difference{value=AcademicAdministration2, path='/entities/0/L1_Entity_Nm/', operation=UPDATED}] format but I want as given above my expected output.

Thank in advance for your time!

JSON 比较 差异

评论

1赞 stdunbar 9/14/2023
What have you tried to this point?
0赞 9/14/2023
I have taken reference from here - smyachenkov.com/posts/json-difference-in-java but it generate result as [Difference{value=AcademicAdministration2, path='/entities/0/L1_Entity_Nm/', operation=UPDATED}] not as my expected output

答: 暂无答案