在 Java 中使用 Gson 合并/扩展 JSON 对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34092373/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 22:29:10  来源:igfitidea点击:

Merge/Extend JSON Objects using Gson in Java

javajsonmergegson

提问by bradvido

Oftentimes, I have a need to merge two JSON Objects (similar to the way jQuery's $.extend()works). However, the Gson library has no built in functionality and they have said they won't implement it.

很多时候,我需要合并两个 JSON 对象(类似于 jQuery 的$.extend()工作方式)。但是,Gson 库没有内置功能,他们表示不会实现它

Doing something like:

做类似的事情:

private void merge(JsonObject firstObj, JsonObject secondObj){
    for(String keyInSecondObj : secondObj.entrySet().keySet()) {
      if(!firstObj.has(keyInSecondObj )){
        firstObj.add(secondMap.get(keyInSecondObj));
    }
}

Is too simple because it doesn't handle recursively merging JsonObjects, doesn't handle conflicts when the key exists in both maps, and has no special handling for non-primitive values such as Arrays.

太简单了,因为它不处理递归合并 JsonObjects,不处理两个映射中都存在键时的冲突,并且对非原始值(例如 Arrays)没有特殊处理。

I failed to find any pre-built solutions to do this. I would prefer to use something that has been thoroughly tested instead of writing my own method, but it must be Gson(not Hymanson or other).

我没有找到任何预先构建的解决方案来做到这一点。我更愿意使用经过彻底测试的东西,而不是编写我自己的方法,但它必须是Gson(而不是 Hymanson 或其他)。

Edit: I ended up writing my own implementation as have added as an answer to this question

编辑:我最终编写了自己的实现,并添加了这个问题答案

This questionis nota duplicate because it's not using Gson (or Java for that matter).

这个问题不是因为它不使用GSON(或Java为此事)中的重复。

回答by bradvido

Here's my first attempt at writing my own static merge method. Feel free to poke holes in it.

这是我第一次尝试编写自己的静态合并方法。随意在里面戳洞。

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.Map;

public class GsonTools {

    public static enum ConflictStrategy {

        THROW_EXCEPTION, PREFER_FIRST_OBJ, PREFER_SECOND_OBJ, PREFER_NON_NULL;
    }

    public static class JsonObjectExtensionConflictException extends Exception {

        public JsonObjectExtensionConflictException(String message) {
            super(message);
        }

    }

    public static void extendJsonObject(JsonObject destinationObject, ConflictStrategy conflictResolutionStrategy, JsonObject ... objs) 
            throws JsonObjectExtensionConflictException {
        for (JsonObject obj : objs) {
            extendJsonObject(destinationObject, obj, conflictResolutionStrategy);
        }
    }

    private static void extendJsonObject(JsonObject leftObj, JsonObject rightObj, ConflictStrategy conflictStrategy) 
            throws JsonObjectExtensionConflictException {
        for (Map.Entry<String, JsonElement> rightEntry : rightObj.entrySet()) {
            String rightKey = rightEntry.getKey();
            JsonElement rightVal = rightEntry.getValue();
            if (leftObj.has(rightKey)) {
                //conflict                
                JsonElement leftVal = leftObj.get(rightKey);
                if (leftVal.isJsonArray() && rightVal.isJsonArray()) {
                    JsonArray leftArr = leftVal.getAsJsonArray();
                    JsonArray rightArr = rightVal.getAsJsonArray();
                    //concat the arrays -- there cannot be a conflict in an array, it's just a collection of stuff
                    for (int i = 0; i < rightArr.size(); i++) {
                        leftArr.add(rightArr.get(i));
                    }
                } else if (leftVal.isJsonObject() && rightVal.isJsonObject()) {
                    //recursive merging
                    extendJsonObject(leftVal.getAsJsonObject(), rightVal.getAsJsonObject(), conflictStrategy);
                } else {//not both arrays or objects, normal merge with conflict resolution
                    handleMergeConflict(rightKey, leftObj, leftVal, rightVal, conflictStrategy);
                }
            } else {//no conflict, add to the object
                leftObj.add(rightKey, rightVal);
            }
        }
    }

    private static void handleMergeConflict(String key, JsonObject leftObj, JsonElement leftVal, JsonElement rightVal, ConflictStrategy conflictStrategy) 
            throws JsonObjectExtensionConflictException {
        {
            switch (conflictStrategy) {
                case PREFER_FIRST_OBJ:
                    break;//do nothing, the right val gets thrown out
                case PREFER_SECOND_OBJ:
                    leftObj.add(key, rightVal);//right side auto-wins, replace left val with its val
                    break;
                case PREFER_NON_NULL:
                    //check if right side is not null, and left side is null, in which case we use the right val
                    if (leftVal.isJsonNull() && !rightVal.isJsonNull()) {
                        leftObj.add(key, rightVal);
                    }//else do nothing since either the left value is non-null or the right value is null
                    break;
                case THROW_EXCEPTION:
                    throw new JsonObjectExtensionConflictException("Key " + key + " exists in both objects and the conflict resolution strategy is " + conflictStrategy);
                default:
                    throw new UnsupportedOperationException("The conflict strategy " + conflictStrategy + " is unknown and cannot be processed");
            }
        }
    }
}

回答by Viacheslav Vedenin

You can use

您可以使用

  Map firstObject = new GSON().fromJson(json1, HashMap.class);
  Map secondObject = new GSON().fromJson(json2, HashMap.class);

// merge Map firstObject and secondObject as you want, see this post

// 根据需要合并 Map firstObject 和 secondObject,请参阅此帖子

  String resultJson = new GSON().toJson(resultMap); 

回答by Luferquisa

You can cast one jsonobject to a jsonaray, (you can check here) then you can add new jsonarray to the other jsonobject or convert both jsonobject to a jsonarray, then create a new jsonobject.

您可以将一个 jsonobject 转换为 jsonaray,(您可以 在此处查看)然后您可以将新的 jsonarray 添加到另一个 jsonobject 或将两个 jsonobject 转换为 jsonarray,然后创建一个新的 jsonobject。

jsonObject.add("property", jsonarray);

回答by Zon

Library class below allows deep merge of arrays and objects. They apply the strategy as described, but you can adopt your own strategy by changing simple operations in the middle of methods.

下面的库类允许数组和对象的深度合并。他们按照描述应用策略,但您可以通过更改方法中间的简单操作来采用自己的策略。

The "Overlay" stratagy of merging (overwrite or add):

合并(覆盖或添加)的“叠加”策略:

  • Primitive type fields are overwritten.
  • Objects with equal declared keys are combined.
  • Objects without declared keys are combined if object contents are equal.
  • Other objects are overwritten or added.
  • Arrays are combined: duplicate items are added in the same array until the amount of such items is equal in both arrays.
  • 原始类型字段被覆盖。
  • 具有相同声明键的对象被合并。
  • 如果对象内容相等,则组合没有声明键的对象。
  • 其他对象被覆盖或添加。
  • 组合数组:将重复项添加到同一个数组中,直到两个数组中此类项目的数量相等。

Usage (Stringor Gson JsonObject/JsonArraycan be returned):

用法(String或 Gson JsonObject/JsonArray可以返回):

// Straightforward object merging:

Json.mergeObjects(    
  "{my_object_as_string}",
  "{my_other_object_as_string}");

// Merge "my_objects" arrays and set object identity keys:

HashMap<String, String[]> keyCombinations = new HashMap<>();
  keyCombinations.put(
    "objects",
    new String[] {"object_identity_key_one", "object_identity_key_two"});

Json.mergeArrays(
  "my_objects",
  keyCombinations,
  "[my_array_as_string]",
  "[my_other_array_as_string]"));

The Library Class:

图书馆类:

package com.example.utils;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.util.ArrayList;
import java.util.HashMap;

import io.reactivex.annotations.NonNull;

@SuppressWarnings("unused")
public class Json {

  /**
   * Merge given JSON-objects. Same keys are merged for objects and 
   * overwritten by last object for primitive types.
   *
   * @param keyCombinations Key names for unique object identification. 
   *                        Or empty collection.
   * @param objects Any amount of JSON-objects to merge.
   *
   * @return Merged JSON-object.
   */
  public static JsonObject mergeObjects(
    @NonNull
      HashMap<String, String[]> keyCombinations,
    Object... objects) {

    JsonObject mergedObject = new JsonObject();

    for (Object object : objects) {

      JsonObject jsonObject = (JsonObject) object;

      for (String key : jsonObject.keySet()) {

        JsonElement parameter = jsonObject.get(key);

        if (mergedObject.has(key)) {

          // Key name matches:

          if (jsonObject.get(key).isJsonObject()) {

            // This is object - merge:

            parameter =
              mergeObjects(
                keyCombinations,
                mergedObject.get(key).getAsJsonObject(),
                jsonObject.get(key).getAsJsonObject());

          } else if (jsonObject.get(key).isJsonArray()) {

            // This is array - merge:

            parameter =
              mergeArrays(
                key,
                keyCombinations,
                mergedObject.get(key).getAsJsonArray(),
                jsonObject.get(key).getAsJsonArray());

          } else {

            // This is neither object nor array - replace value:

            mergedObject.add(key, parameter);
          }
        }

        // No such field yet - add:

        mergedObject.add(key, parameter);
      }
    }

    return mergedObject;
  }

  /**
   * Alternative - no object identity keys are set.
   * See {@link Json#mergeObjects(HashMap, Object...)}
   */
  public static JsonObject mergeObjects(
    Object... objects) {

    return (
      mergeObjects(
        new HashMap<>(),
        objects));
  }

  /**
   * Get GSON-object from string.
   *
   * @param jsonString JSON-object as string.
   *
   * @return JsonObject (GSON).
   */
  public static JsonObject getJsonObject(String jsonString) {

    JsonObject jsonObject = new JsonObject();
    JsonParser parser;

    parser = new JsonParser();

    if (jsonString != null) {

      jsonObject =
        parser
          .parse(
            jsonString)
          .getAsJsonObject();
    }

    return jsonObject;
  }

  /**
   * See {@link Json#mergeObjects(HashMap, Object...)}
   */
  public static String mergeObjects(
    HashMap<String, String[]> keyCombinations,
    String... jsonObjects) {

    ArrayList<JsonObject> objects = new ArrayList<>();

    for (String jsonObject : jsonObjects) {

      objects.add(
        Json2.getJsonObject(jsonObject));
    }

    return (
      mergeObjects(
        keyCombinations,
        objects.toArray())
        .toString());
  }

  /**
   * Alternative - no object identity keys are set.
   * See {@link Json#mergeObjects(HashMap, Object...)}
   */
  public static String mergeObjects(
    String... jsonObjects) {

    ArrayList<JsonObject> objects = new ArrayList<>();

    for (String jsonObject : jsonObjects) {

      objects.add(
        getJsonObject(jsonObject));
    }

    return (
      mergeObjects(
        new HashMap<>(),
        objects.toArray())
        .toString());
  }

  /**
   * See {@link Json#mergeArrays(String, HashMap, Object...)}
   */
  public static String mergeArrays(
    String arrayName,
    HashMap<String, String[]> keyCombinations,
    String... jsonArrays) {

    ArrayList<JsonArray> arrays = new ArrayList<>();

    for (String jsonArray : jsonArrays) {

      arrays.add(
        getJsonArray(jsonArray));
    }

    return (
      mergeArrays(
        arrayName,
        keyCombinations,
        arrays.toArray())
        .toString());
  }

  /**
   * Alternative - no object identity keys are set.
   * See {@link Json#mergeArrays(String, HashMap, Object...)}
   */
  public static String mergeArrays(
    String... jsonArrays) {

    ArrayList<JsonArray> arrays = new ArrayList<>();

    for (String jsonArray : jsonArrays) {

      arrays.add(
        getJsonArray(jsonArray));
    }

    return (
      mergeArrays(
        "",
        new HashMap<>(),
        arrays.toArray())
        .toString());
  }

  /**
   * Alternative - no object identity keys are set.
   * Seee {@link Json#mergeArrays(String, HashMap, Object...)}
   */
  public static JsonArray mergeArrays(
    Object... jsonArrays) {

    return (
      mergeArrays(
        "",
        new HashMap<>(),
        jsonArrays));
  }

  /**
   * Merge arrays following "Overlay" strategy (overwrite or add).
   * Duplicate elements are added to array until their amount is equal 
   * in both arrays. Objects are considered identical if their
   * identifier-keys are present and their values are equal. If no such 
   * keys, then objects are considered identical on equal content.
   *
   * @param arrayName       Merged arrays name or empty string. 
   *                        Used to choose from key combinations.
   * @param keyCombinations Array objects identifier-key names.
   * @param jsonArrays      Any amount of JSON-arrays to merge.
   *
   * @return Merged array.
   */
  public static JsonArray mergeArrays(
    @NonNull
      String arrayName,
    @NonNull
      HashMap<String, String[]> keyCombinations,
    Object... jsonArrays) {

    JsonArray resultArray = new JsonArray();

    for (Object jsonArray : jsonArrays) {

      JsonArray array = (JsonArray) jsonArray;

      for (JsonElement item : array) {

        if (
          item.isJsonObject() &&
          keyCombinations.get(arrayName) != null &&
          keyCombinations.get(arrayName).length > 0) {

          // Array element is an object with identifier-keys:

          ArrayList<JsonElement> resultArrayObjectsFound =
            getArrayObjectsByKeyValues(
              resultArray,
              item.getAsJsonObject(),
              keyCombinations.get(arrayName));

          if (resultArrayObjectsFound.size() > 0) {

            // Such field is already present, merge is required:

            JsonObject resultArrayObjectFound =
              resultArrayObjectsFound.get(0).getAsJsonObject();

            JsonObject mergedObject =
              mergeObjects(
                keyCombinations,
                resultArrayObjectFound,
                item.getAsJsonObject());

            resultArray.remove(resultArrayObjectFound);
            resultArray.add(mergedObject);

            continue;
          }
        }

        if (!resultArray.contains(item)) {

          // No such element - add:

          resultArray.add(item);
        } else if (
          count(resultArray, item) < count(array, item)) {

          // There are more duplicates of the element - add:

          resultArray.add(item);
        }
      }
    }

    return resultArray;
  }

  /**
   * Convert String to JSON-Array (GSON).
   *
   * @param jsonString JSON-array as string.
   *
   * @return JSON-array as GSON-array.
   */
  public static JsonArray getJsonArray(String jsonString) {

    JsonArray jsonArray = new JsonArray();
    JsonParser parser;

    parser =  new JsonParser();

    try {

      jsonArray =
        parser
          .parse(
            jsonString)
          .getAsJsonArray();

    } catch (Exception ignore) {
    }

    return jsonArray;
  }

  /**
   * Find array objects that have required identity keys and match the values.
   *
   * @param array  Array to search in.
   * @param object Example object for search. 
   *               Contains required keys and values. 
   * @param keys   Object identity keys.
   *
   * @return Matching JSON-elements.
   */
  public static ArrayList<JsonElement> getArrayObjectsByKeyValues(
    JsonArray array,
    JsonObject object,
    String[] keys) {

    ArrayList<JsonElement> elements = new ArrayList<>();

    for (JsonElement arrayElement : array) {

      if (arrayElement.isJsonObject()) {

        JsonObject jsonObject = arrayElement.getAsJsonObject();

        boolean hasAllKeysThatMatch = true;

        for (String key : keys) {

          if (!jsonObject.has(key)) {

            // One of the keys is not found:

            hasAllKeysThatMatch = false;

            break;
          } else {

            if (
              jsonObject.get(key).isJsonPrimitive() &&
              !jsonObject.get(key).equals(object.get(key))) {

              // Primitive type key values don't match:

              hasAllKeysThatMatch = false;

              break;
            }

            if ((
                  jsonObject.get(key).isJsonObject() ||
                  jsonObject.get(key).isJsonArray()) &&
                !jsonObject.get(key).toString().equals(
                  object.get(key).toString())) {

              // Complex type key values don't match:

              hasAllKeysThatMatch = false;

              break;
            }
          }
        }

        if (hasAllKeysThatMatch) {

          // Key values match:

          elements.add(jsonObject);
        }
      }
    }

    return elements;
  }

  /**
   * Count given elements in array.
   *
   * @param element Element to find.
   *
   * @return Amount of given elements in array.
   */
  public static int count(
    JsonArray array,
    JsonElement element) {

    int count = 0;

    for (JsonElement currentElement : array) {

      if (currentElement.isJsonPrimitive()) {

        // Primitive type:

        if (currentElement.equals(element)) {

          count++;
        }
      }

      if (
        currentElement.isJsonObject() ||
        currentElement.isJsonArray()) {

        // Complex type:

        if (currentElement.toString().equals(element.toString())) {

          count++;
        }
      }
    }

    return count;
  }

}