【问题标题】:"Deep replace" with javax.json.JsonObject用 javax.json.JsonObject 进行“深度替换”
【发布时间】:2020-12-16 14:24:42
【问题描述】:

无法直接替换 javax.json.JsonObject 中的值,因为 javax.json.JsonObject 实现了不可变映射。为此,您必须创建一个新的 JsonObject 并将值从原始值复制到新的值中,并注意替换您要替换的值。 我找到了如何使用“简单”JsonObject 执行此操作的示例,其中没有嵌套的 JsonObjects。我正在寻找的是一个通用的替换实现,其中我传递了一个 JsonObject、属性名称和新值。此方法应该“遍历” JsonObject 并替换属性(无论它在对象层次结构中的哪个位置)并保持其他属性不变。 例如。这是我原来的 JsonObject

{
   "Attr1":number1,
   "Attr2":number2,
   "Attr3":number3,
   "Attr4":[
      "string1"
   ],
   "Attr5":[
      {
         "Attr6":[
            {
               "Attr7":"string2",
               "Attr8":"string3",
               "$Attr9":number4
            },
            {
               "Attr7":"string4",
               "Attr8":"string5",
               "Attr9":number5
            }
         ],
         "Attr10":number6,
         "Attr14":{
            "Attr10":"string6",
            "Attr11":"string7",
            "Attr12":"string8"
         },
         "Attr13":[
            "string9",
            "string10"
         ],
         "Attr14":"string11"
      }
   ]
}

我想用字符串数组而不是 JsonObjects 数组替换 Attr6:

"Attr6":["newString1","newString2"],

相应的调用可能类似于 replaceValue(JsonObject jObj, String attrName, JsonValue newValue)) ,其中“jObj”是整个 Json,“attrName”是“Attr6”,“newValue”是包含两个字符串的 JsonArray。

有人可以指出一个实现此类功能的示例或帮助我吗?

我自己尝试过,但它并没有真正起作用,因为每次递归迭代都会重新创建构建器(或者更可能是因为它全错了...... :))

public static JsonObject replaceValue( final JsonObject jsonObject, final String jsonKey, final JsonValue jsonValue )
{
  JsonObjectBuilder builder = Json.createObjectBuilder();

  if(jsonObject == null)
  {
    return builder.build();
  }

  Iterator<Entry<String, JsonValue>> it = jsonObject.entrySet().iterator();

  while (it.hasNext())
  {
    @SuppressWarnings( "rawtypes" )
    JsonObject.Entry mapEntry = it.next();

    if (mapEntry.getKey() == jsonKey)
    {
      builder.add(jsonKey, jsonValue);
    }
    else if (ValueType.STRING.equals(((JsonValue) mapEntry.getValue()).getValueType()) || ValueType.NUMBER.equals(((JsonValue) mapEntry.getValue()).getValueType()) || ValueType.TRUE.equals(((JsonValue) mapEntry.getValue()).getValueType()) ||
        ValueType.FALSE.equals(((JsonValue) mapEntry.getValue()).getValueType()) || (JsonValue) mapEntry.getValue() == null || "schemas".equalsIgnoreCase((String) mapEntry.getKey()))
    {
      builder.add(mapEntry.getKey().toString(), (JsonValue) mapEntry.getValue());
    }
    else if (ValueType.OBJECT.equals(((JsonValue) mapEntry.getValue()).getValueType()))
    {
      JsonObject modifiedJsonobject = (JsonObject) mapEntry.getValue();
      if (modifiedJsonobject != null)
      {
        replaceValue(modifiedJsonobject, jsonKey, jsonValue);
      }
    }
    else if (ValueType.ARRAY.equals(((JsonValue) mapEntry.getValue()).getValueType()))
    {
      for (int i = 0; i < ((JsonValue) mapEntry.getValue()).asJsonArray().size(); i++)
      {
        replaceValue((JsonObject) ((JsonValue) mapEntry.getValue()).asJsonArray().get(i), jsonKey, jsonValue);
      }
    }
  }

  return builder.build();
}

【问题讨论】:

  • 如果有多个同名的键嵌套在不同的 JSON 对象中怎么办?那该换哪一个呢?
  • 假设key在整个JsonObject中是唯一的。
  • 您是否仅限于使用javax.json,还是可以使用其他库,例如 Jackson 或 Gson?
  • 是的,我必须使用 javax.json

标签: java json


【解决方案1】:

这是解决问题的另一种方法,它使用javax.json.stream.JsonParser 提供的流解析器。这会从 JSON 源生成一个令牌流,其中包含描述令牌类型的 javax.json.stream.JsonParser.Event 值(例如 START_OBJECTKEY_NAME 等)。

对我们来说最重要的是,解析器上有 skipObject()skipArray() 方法,它们允许我们删除源 JSON 中不需要的部分。

总体方法是逐个令牌构建 JSON 的新版本作为字符串,当我们到达 JSON 中的相关位置(或多个位置)时替换替换部分。

最后,我们将新字符串转换回一个对象,这样我们就可以漂亮地打印它了。

这种方法没有使用递归。

import java.io.IOException;
import javax.json.Json;
import javax.json.stream.JsonParser;
import javax.json.stream.JsonParser.Event;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import javax.json.JsonObject;
import javax.json.JsonWriterFactory;
import javax.json.stream.JsonGenerator;

public class StreamDemo {

    public static void doStream() throws IOException {
        JsonParser jsonParser = Json.createParser(new StringReader(JSONSTRING));

        StringBuilder sb = new StringBuilder();
        Event previous = null;

        String targetKeyName = "Attr6";
        String replacement = "[\"newString1\",\"newString2\"]";
        // This event reflects the end of the "replacement" string - namely "]".
        // We need this because this event may be different from the replaced event.
        Event replacementPreviousEvent = Event.END_ARRAY;

        // Used when we find the target key for replacement:
        boolean doReplacement = false;

        while (jsonParser.hasNext()) {
            Event event = jsonParser.next();
            if (doReplacement) {
                // Skip over the structure we want to replace:
                if (event.equals(Event.START_OBJECT)) {
                    jsonParser.skipObject();
                } else if (event.equals(Event.START_ARRAY)) {
                    jsonParser.skipArray();
                }
                // Write the replacement fragment here:
                sb.append(replacement);
                // Move to the next event in the stream:
                event = jsonParser.next();
                previous = replacementPreviousEvent;
                doReplacement = false;
            }

            if (Event.KEY_NAME.equals(event)
                    && jsonParser.getString().equals(targetKeyName)) {
                doReplacement = true;
            }

            switch (event) {
                case START_OBJECT:
                    if (Event.END_OBJECT.equals(previous)) {
                        sb.append(",");
                    }
                    sb.append("{");
                    break;
                case END_OBJECT:
                    sb.append("}");
                    break;
                case START_ARRAY:
                    sb.append("[");
                    break;
                case END_ARRAY:
                    sb.append("]");
                    break;
                case KEY_NAME:
                    sb = previousWasAValue(previous, sb);
                    sb = previousWasAnEnd(previous, sb);
                    sb.append("\"").append(jsonParser.getString()).append("\":");
                    break;
                case VALUE_STRING:
                    sb = previousWasAValue(previous, sb);
                    sb.append("\"").append(jsonParser.getString()).append("\"");
                    break;
                case VALUE_NUMBER:
                    sb = previousWasAValue(previous, sb);
                    if (jsonParser.isIntegralNumber()) {
                        sb.append(jsonParser.getLong());
                    } else {
                        sb.append(jsonParser.getBigDecimal().toPlainString());
                    }
                    break;
                case VALUE_TRUE:
                    sb = previousWasAValue(previous, sb);
                    sb.append("true");
                    break;
                case VALUE_FALSE:
                    sb = previousWasAValue(previous, sb);
                    sb.append("false");
                    break;
                case VALUE_NULL:
                    sb = previousWasAValue(previous, sb);
                    sb.append("null");
                    break;
                default:
                    break;
            }
            previous = event;
        }
        
        // At the end, pretty-print the new JSON:
        JsonObject modifiedObject = Json.createReader(new StringReader(sb.toString())).readObject();
        Map<String, Boolean> config = new HashMap<>();
        config.put(JsonGenerator.PRETTY_PRINTING, true);
        String jsonString;
        JsonWriterFactory writerFactory = Json.createWriterFactory(config);
        try ( Writer writer = new StringWriter()) {
            writerFactory.createWriter(writer).write(modifiedObject);
            jsonString = writer.toString();
        }
        System.out.println(jsonString);
    }

    private static StringBuilder previousWasAValue(Event previous, StringBuilder sb) {
        // The current value follows another value - so a separating comma is needed:
        if (Event.VALUE_STRING.equals(previous)
                || Event.VALUE_NUMBER.equals(previous)
                || Event.VALUE_TRUE.equals(previous)
                || Event.VALUE_FALSE.equals(previous)
                || Event.VALUE_NULL.equals(previous)) {
            sb.append(",");
        }
        return sb;
    }

    private static StringBuilder previousWasAnEnd(Event previous, StringBuilder sb) {
        // The current key follows the end of an object or an array, so a 
        // separating comma is needed:
        if (Event.END_OBJECT.equals(previous)
                || Event.END_ARRAY.equals(previous)) {
            sb.append(",");
        }
        return sb;
    }

    private static final String JSONSTRING
            = """
    {
       "Attr0": null,
       "Attr1": true,
       "Attr2": false,
       "Attr3": 3,
       "Attr4": [
               "string1"
       ],
       "Attr5": [{
               "Attr6": [{
                               "Attr7": "string2",
                               "Attr8": "string3",
                               "Attr9": 4
                       },
                       {
                               "Attr7": "string4",
                               "Attr8": "string5",
                               "Attr9": 5
                       }
               ],
               "Attr10": 6,
               "Attr14": {
                       "Attr10": "string6",
                       "Attr11": "string7",
                       "Attr12": "string8"
               },
               "Attr13": [
                       "string9",
                       123.45,
                       false
               ],
               "Attr15": "string11"
       }]
    }
    """;

}

【讨论】:

    【解决方案2】:

    在从 Kolban 在这篇帖子 Convert a JSON String to a HashMap 中的回答中得到启发后,我应该找到了解决方案:

    public class JsonUtils
    {
    
      public static JsonObject replaceValue( final JsonObject jsonObject, final String jsonKey, final Object jsonValue )
      {
        JsonObjectBuilder builder = Json.createObjectBuilder();
    
        if (jsonObject != JsonObject.NULL)
        {
          builder = replace(jsonObject, jsonKey, jsonValue, builder);
        }
    
        return builder.build();
      }
    
      private static JsonObjectBuilder replace( final JsonObject jsonObject, final String jsonKey, final Object jsonValue, final JsonObjectBuilder builder )
      {
        Iterator<Entry<String, JsonValue>> it = jsonObject.entrySet().iterator();
    
        while (it.hasNext())
        {
          @SuppressWarnings( "rawtypes" )
          JsonObject.Entry mapEntry = it.next();
    
          String key = mapEntry.getKey().toString();
          Object value = mapEntry.getValue();
    
          if (key.equalsIgnoreCase(jsonKey))
          {
            if (jsonValue instanceof String)
            {
              builder.add(jsonKey, (String) jsonValue);
            }
            else
            {
              builder.add(jsonKey, (JsonValue) jsonValue);
            }
            
            // here you can add the missing casting you need
    
            continue;
          }
    
          if (value instanceof JsonArray)
          {
            value = toJsonArray((JsonArray) value, jsonKey, jsonValue, builder);
          }
          else if (value instanceof JsonObject)
          {
            JsonObjectBuilder newBuilder = Json.createObjectBuilder();
    
            value = replace((JsonObject) value, jsonKey, jsonValue, newBuilder);
    
            if (value instanceof JsonObjectBuilder)
            {
              value = ((JsonObjectBuilder) value).build();
            }
          }
    
          builder.add(key, (JsonValue) value);
        }
    
        return builder;
      }
    
      private static JsonArray toJsonArray( final JsonArray array, final String jsonKey, final Object jsonValue, final JsonObjectBuilder builder )
      {
        JsonArrayBuilder jArray = Json.createArrayBuilder();
    
        for (int i = 0; i < array.size(); i++)
        {
          Object value = array.get(i);
    
          if (value instanceof JsonArray)
          {
            value = toJsonArray((JsonArray) value, jsonKey, jsonValue, builder);
          }
          else if (value instanceof JsonObject)
          {
            JsonObjectBuilder newBuilder = Json.createObjectBuilder();
    
            value = replace((JsonObject) value, jsonKey, jsonValue, newBuilder);
    
            if (value instanceof JsonObjectBuilder)
            {
              value = ((JsonObjectBuilder) value).build();
            }
          }
    
          jArray.add((JsonValue) value);
        }
    
        return jArray.build();
      }
    

    请记住,如果您要替换的键在整个 JsonObject 中是唯一的,则此方法有效。

    任何改进都值得赞赏......

    【讨论】:

      【解决方案3】:

      如果您不想使用流 API(在我的其他答案中使用),我认为您可以使用JsonObjectBuilderJsonOArrayBuilder 实现更紧凑的方法 - 与您的方法类似递归:

      private static JsonStructure iterate(final JsonStructure json) {
          if (json.getValueType().equals(ValueType.OBJECT)) {
              JsonObjectBuilder builder = Json.createObjectBuilder();
              json.asJsonObject().forEach((key, value) -> {
                  switch (value.getValueType()) {
                      case OBJECT:
                          if (key.equals(targetKey)) {
                              builder.add(key, replacementJson);
                          } else {
                              builder.add(key, iterate(value.asJsonObject()));
                          }   break;
                      case ARRAY:
                          if (key.equals(targetKey)) {
                              builder.add(key, replacementJson);
                          } else {
                              builder.add(key, iterate(value.asJsonArray()));
                          }   break;
                      default:
                          if (key.equals(targetKey)) {
                              builder.add(key, replacementJson);
                          } else {
                              builder.add(key, value);
                          }   break;
                  }
              });
              return builder.build();
          } else if (json.getValueType().equals(ValueType.ARRAY)) {
              JsonArrayBuilder builder = Json.createArrayBuilder();
              json.asJsonArray().forEach((value) -> {
                  switch (value.getValueType()) {
                      case OBJECT:
                          builder.add(iterate(value.asJsonObject()));
                          break;
                      case ARRAY:
                          builder.add(iterate(value.asJsonArray()));
                          break;
                      default:
                          builder.add(value);
                          break;
                  }
              });
              return builder.build();
          }
          return null;
      }
      

      就我个人而言,阅读此递归代码比阅读其他答案中的流式代码更难。但它肯定更简洁。

      它的工作原理是向下迭代到每个 JSON 对象和数组的嵌套级别,然后从最深的嵌套级别向外构建原始数据的副本。当它找到指定的替换键时,它使用相关的替换 JSON 作为键的值。

      可以按如下方式调用上述方法 - 漂亮地打印最终结果:

      final JsonStructure jsonOriginal = Json.createReader(new StringReader(JSONSTRING)).readObject();
      
      final JsonStructure jsonCopy = iterate(jsonOriginal);
      
      Map<String, Boolean> config = new HashMap<>();
      config.put(JsonGenerator.PRETTY_PRINTING, true);
      String jsonString;
      JsonWriterFactory writerFactory = Json.createWriterFactory(config);
      try ( Writer writer = new StringWriter()) {
          writerFactory.createWriter(writer).write(jsonCopy);
          jsonString = writer.toString();
      }
      System.out.println(jsonString);
      

      对于我的替换 JSON,我使用了这个,显示了一些测试数据示例:

      private final static String targetKey = "Attr6";
      
      //private final static JsonStructure replacementJson = Json.createArrayBuilder()
      //        .add("newString1")
      //        .add("newString2").build();
      
      private final static JsonStructure replacementJson = Json.createObjectBuilder()
              .add("newkey1", "newString1")
              .add("newkey2", "newString2").build();
      

      因此,使用与我的其他答案相同的起始 JSON,此代码会生成以下内容:

      {
          "Attr0": null,
          "Attr1": true,
          "Attr2": false,
          "Attr3": 3,
          "Attr4": [
              "string1"
          ],
          "Attr5": [
              {
                  "Attr6": {
                      "newkey1": "newString1",
                      "newkey2": "newString2"
                  },
                  "Attr10": 6,
                  "Attr14": {
                      "Attr10": "string6",
                      "Attr11": "string7",
                      "Attr12": "string8"
                  },
                  "Attr13": [
                      "string9",
                      123.45,
                      false
                  ],
                  "Attr15": "string11"
              }
          ]
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-02-22
        • 1970-01-01
        • 1970-01-01
        • 2013-03-11
        • 2017-01-07
        • 2018-03-28
        • 1970-01-01
        相关资源
        最近更新 更多