【问题标题】:Handling unknown Json properties when mapping Json object to POJO将 Json 对象映射到 POJO 时处理未知的 Json 属性
【发布时间】:2018-06-05 10:55:48
【问题描述】:

我有一个看起来像这样的 POJO:

public class MyClass {
    public String sub;
    public String iss;
    public JsonObject customProperties;

    ..constructor, getters, setters..
}

还有一个看起来像这样的 Json 对象:

{
    "sub" : "value",
    "iss" : "value2",
    "unknown_property" : "value3",
    "unknown_property_2" : {
        "a" : 1,
        "b" : 2
    }
}

我想要将 Json 对象映射到 MyClass,其中 Json 中的任何未知属性都将添加到“customProperties”字段。 (注意!我也可以将“customProperties”作为地图,但请记住对象是未知的并且可能很复杂)。

MyClass 对象将如下所示:

sub = "value"
iss = "value2"
customProperties = {"unknown_property":"value3","unknown_property_2":{"a":1,"b":2}}

我在想我需要一个自定义反序列化器,但我尝试过的东西不能满足我的需求,或者我可能缺乏理解/能力。

我可以使用 google.gson 或 org.json,不是 jackson。

【问题讨论】:

    标签: java json gson mapping org.json


    【解决方案1】:

    这需要一个特殊的后处理类型适配器,它可以进行反序列化和收集未知属性。 我会这样做类似于以下内容。

    下面的接口是一个真实对象和类型适配器之间的桥梁。 这里没什么特别的。

    interface IUnknownPropertiesConsumer {
    
        void acceptUnknownProperties(JsonObject jsonObject);
    
    }
    

    现在,您可以拥有实现上述接口的以下映射。

    final class MyClass
            implements IUnknownPropertiesConsumer {
    
        final String sub = null;
        final String iss = null;
        transient JsonObject customProperties;
    
        @Override
        public void acceptUnknownProperties(final JsonObject customProperties) {
            this.customProperties = customProperties;
        }
    
    }
    

    请注意,如果您由于某种原因无法更改映射,您仍然可以调整以下类型适配器来解决问题:

    final class UnknownPropertiesTypeAdapterFactory
            implements TypeAdapterFactory {
    
        private static final TypeAdapterFactory instance = new UnknownPropertiesTypeAdapterFactory();
    
        private UnknownPropertiesTypeAdapterFactory() {
        }
    
        static TypeAdapterFactory get() {
            return instance;
        }
    
        @Override
        public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
            // Check if we can deal with the given type
            if ( !IUnknownPropertiesConsumer.class.isAssignableFrom(typeToken.getRawType()) ) {
                return null;
            }
            // If we can, we should get the backing class to fetch its fields from
            @SuppressWarnings("unchecked")
            final Class<IUnknownPropertiesConsumer> rawType = (Class<IUnknownPropertiesConsumer>) typeToken.getRawType();
            @SuppressWarnings("unchecked")
            final TypeAdapter<IUnknownPropertiesConsumer> delegateTypeAdapter = (TypeAdapter<IUnknownPropertiesConsumer>) gson.getDelegateAdapter(this, typeToken);
            // Excluder is necessary to check if the field can be processed
            // Basically it's not required, but it makes the check more complete 
            final Excluder excluder = gson.excluder();
            // This is crucial to map fields and JSON object properties since Gson supports name remapping
            final FieldNamingStrategy fieldNamingStrategy = gson.fieldNamingStrategy();
            final TypeAdapter<IUnknownPropertiesConsumer> unknownPropertiesTypeAdapter = UnknownPropertiesTypeAdapter.create(rawType, delegateTypeAdapter, excluder, fieldNamingStrategy);
            @SuppressWarnings("unchecked")
            final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) unknownPropertiesTypeAdapter;
            return castTypeAdapter;
        }
    
        private static final class UnknownPropertiesTypeAdapter<T extends IUnknownPropertiesConsumer>
                extends TypeAdapter<T> {
    
            private final TypeAdapter<T> typeAdapter;
            private final Collection<String> propertyNames;
    
            private UnknownPropertiesTypeAdapter(final TypeAdapter<T> typeAdapter, final Collection<String> propertyNames) {
                this.typeAdapter = typeAdapter;
                this.propertyNames = propertyNames;
            }
    
            private static <T extends IUnknownPropertiesConsumer> TypeAdapter<T> create(final Class<? super T> clazz, final TypeAdapter<T> typeAdapter,
                    final Excluder excluder, final FieldNamingStrategy fieldNamingStrategy) {
                final Collection<String> propertyNames = getPropertyNames(clazz, excluder, fieldNamingStrategy);
                return new UnknownPropertiesTypeAdapter<>(typeAdapter, propertyNames);
            }
    
            @Override
            public void write(final JsonWriter out, final T value)
                    throws IOException {
                typeAdapter.write(out, value);
            }
    
            @Override
            public T read(final JsonReader in) {
                // JsonParser holds no state so instantiation is a bit excessive, but Gson may change in the future
                final JsonParser jsonParser = new JsonParser();
                // In its simplest solution, we can just collect a JSON tree because its much easier to process
                final JsonObject jsonObjectToParse = jsonParser.parse(in).getAsJsonObject();
                final JsonObject unknownProperties = new JsonObject();
                for ( final Map.Entry<String, JsonElement> e : jsonObjectToParse.entrySet() ) {
                    final String propertyName = e.getKey();
                    // No in the object fields?
                    if ( !propertyNames.contains(propertyName) ) {
                        // Then we assume the property is unknown
                        unknownProperties.add(propertyName, e.getValue());
                    }
                }
                // First convert the above JSON tree to an object
                final T object = typeAdapter.fromJsonTree(jsonObjectToParse);
                // And do the post-processing
                object.acceptUnknownProperties(unknownProperties);
                return object;
            }
    
            private static Collection<String> getPropertyNames(final Class<?> clazz, final Excluder excluder, final FieldNamingStrategy fieldNamingStrategy) {
                final Collection<String> propertyNames = new ArrayList<>();
                // Class fields are declared per class so we have to traverse the whole hierarachy
                for ( Class<?> i = clazz; i.getSuperclass() != null && i != Object.class; i = i.getSuperclass() ) {
                    for ( final Field declaredField : i.getDeclaredFields() ) {
                        // If the class field is not excluded
                        if ( !excluder.excludeField(declaredField, false) ) {
                            // We can translate the field name to its property name counter-part
                            final String propertyName = fieldNamingStrategy.translateName(declaredField);
                            propertyNames.add(propertyName);
                        }
                    }
                }
                return propertyNames;
            }
    
        }
    
    }
    

    现在一次性全部使用:

    private static final Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(UnknownPropertiesTypeAdapterFactory.get())
            .create();
    
    ...
    
    // Assuming jsonReader is a reader to read your original JSON
    final MyClass o = gson.fromJson(jsonReader, MyClass.class);
    System.out.println(o.sub);
    System.out.println(o.iss);
    System.out.println(o.customProperties);
    

    那么输出如下:

    value
    value2
    {"unknown_property":"value3","unknown_property_2":{"a":1,"b":2}}
    

    【讨论】:

      【解决方案2】:

      您可以为此使用 JSON-java。

      JSONObject jsonObject = new JSONObject(jsonString);
      MyClass myPojo =  new MyClass();
      
      myPojo.setSub(jsonObject.getString("sub"));
      myPojo.setIss(jsonObject.getString("iss"));
      myPojo.setCustomProperties(new JSONObject());
      
      String[] keys = JSONObject.getNames(jsonObject);
      
      for (String key : keys) 
          if(!(key.equals("sub") ||  key.equals("iss")))
              myPojo.getCustomProperties().put(key, jsonObject.get(key));
      

      【讨论】:

      • 这绝对有效,但我希望有一种更优雅的方式来做到这一点
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-27
      • 1970-01-01
      • 2020-02-10
      • 2015-06-04
      相关资源
      最近更新 更多