将Json对象映射到POJO时处理未知的Json属性

时间:2018-06-05 10:55:49

标签: java json gson mapping org.json

我有一个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,不是杰克逊。

2 个答案:

答案 0 :(得分:2)

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

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

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}}

答案 1 :(得分:0)

您可以使用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));