如何将 JSON 对象子对象序列化为字段?

Joh*_*upe 4 android gson

我有一个看起来像这样的 JSON 对象

{
  "foo":{
      "bar":"bar",
      "echo":"echo"
  }
}
Run Code Online (Sandbox Code Playgroud)

但是我的 Java 对象看起来像这样:

{
  "foo":{
      "bar":"bar",
      "echo":"echo"
  }
}
Run Code Online (Sandbox Code Playgroud)

我想echo直接序列化为foo. 这样的事情可能吗:

class Foo {
    public String foo2;
}
Run Code Online (Sandbox Code Playgroud)

或者我如何使用自定义解串器来做到这一点?

Lyu*_*riv 5

作为替代方法,您还可以创建自己的类型适配器,以便将 JSON 表达式应用于不存在的字段。如果您可以自由地向您正在处理的项目添加新库,它可以基于JsonPath

拥有这样一个非标准类型的适配器,您可以省略直接绑定到缺失字段的中间映射类:

final class Foo {

    // or @JsonPathExpression("foo.echo")
    @JsonPathExpression("$.foo.echo")
    String foo2;

}
Run Code Online (Sandbox Code Playgroud)

@JsonPathExpression是自定义注释,可以自己处理(JsonPath可以是较短的名称,但它已被 JsonPath 库占用,以免造成混淆):

@Retention(RUNTIME)
@Target(FIELD)
@interface JsonPathExpression {

    String value();

}
Run Code Online (Sandbox Code Playgroud)

类型适配器允许编写复杂的序列化/反序列化策略,它们的特点之一是可以将它们组合起来编写后处理器,例如,可以处理自定义注释。

final class JsonPathTypeAdapterFactory
        implements TypeAdapterFactory {

    // The type adapter factory is stateless so it can be instantiated once
    private static final TypeAdapterFactory jsonPathTypeAdapterFactory = new JsonPathTypeAdapterFactory();

    private JsonPathTypeAdapterFactory() {
    }

    static TypeAdapterFactory getJsonPathTypeAdapterFactory() {
        return jsonPathTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Pick up the down stream type adapter to avoid infinite recursion
        final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
        // Collect @JsonPathExpression-annotated fields
        final Collection<FieldInfo> fieldInfos = FieldInfo.of(typeToken.getRawType());
        // If no such fields found, then just return the delegated type adapter
        // Otherwise wrap the type adapter in order to make some annotation processing
        return fieldInfos.isEmpty()
                ? delegateAdapter
                : new JsonPathTypeAdapter<>(gson, delegateAdapter, gson.getAdapter(JsonElement.class), fieldInfos);
    }

    private static final class JsonPathTypeAdapter<T>
            extends TypeAdapter<T> {

        private final Gson gson;
        private final TypeAdapter<T> delegateAdapter;
        private final TypeAdapter<JsonElement> jsonElementTypeAdapter;
        private final Collection<FieldInfo> fieldInfos;

        private JsonPathTypeAdapter(final Gson gson, final TypeAdapter<T> delegateAdapter, final TypeAdapter<JsonElement> jsonElementTypeAdapter,
                final Collection<FieldInfo> fieldInfos) {
            this.gson = gson;
            this.delegateAdapter = delegateAdapter;
            this.jsonElementTypeAdapter = jsonElementTypeAdapter;
            this.fieldInfos = fieldInfos;
        }

        @Override
        public void write(final JsonWriter out, final T value)
                throws IOException {
            // JsonPath can only read by expression, but not write by expression, so we can only write it as it is...
            delegateAdapter.write(out, value);
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            // Building the original JSON tree to keep *all* fields
            final JsonElement outerJsonElement = jsonElementTypeAdapter.read(in).getAsJsonObject();
            // Deserialize the value, not-existing fields will be omitted
            final T value = delegateAdapter.fromJsonTree(outerJsonElement);
            for ( final FieldInfo fieldInfo : fieldInfos ) {
                try {
                    // Resolving JSON element by a JSON path expression
                    final JsonElement innerJsonElement = fieldInfo.jsonPath.read(outerJsonElement);
                    // And convert it to the field type
                    final Object innerValue = gson.fromJson(innerJsonElement, fieldInfo.field.getType());
                    // Since now it's what can be assigned to the object field...
                    fieldInfo.field.set(value, innerValue);
                } catch ( final PathNotFoundException ignored ) {
                    // if no path given, then just ignore the assignment to the field
                } catch ( final IllegalAccessException ex ) {
                    throw new IOException(ex);
                }
            }
            return value;
        }

    }

    private static final class FieldInfo {

        private final Field field;
        private final JsonPath jsonPath;

        private FieldInfo(final Field field, final JsonPath jsonPath) {
            this.field = field;
            this.jsonPath = jsonPath;
        }

        // Scan the given class for the JsonPathExpressionAnnotation
        private static Collection<FieldInfo> of(final Class<?> clazz) {
            Collection<FieldInfo> collection = emptyList();
            for ( final Field field : clazz.getDeclaredFields() ) {
                final JsonPathExpression jsonPathExpression = field.getAnnotation(JsonPathExpression.class);
                if ( jsonPathExpression != null ) {
                    if ( collection.isEmpty() ) {
                        collection = new ArrayList<>();
                    }
                    field.setAccessible(true);
                    collection.add(new FieldInfo(field, compile(jsonPathExpression.value())));
                }
            }
            return collection;
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

现在必须配置 Gson 和 JsonPath(后者默认不使用 Gson):

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getJsonPathTypeAdapterFactory())
        .create();

static {
    final JsonProvider jsonProvider = new GsonJsonProvider(gson);
    final MappingProvider gsonMappingProvider = new GsonMappingProvider(gson);
    Configuration.setDefaults(new Configuration.Defaults() {
        @Override
        public JsonProvider jsonProvider() {
            return jsonProvider;
        }

        @Override
        public MappingProvider mappingProvider() {
            return gsonMappingProvider;
        }

        @Override
        public Set<Option> options() {
            return EnumSet.noneOf(Option.class);
        }
    });

}
Run Code Online (Sandbox Code Playgroud)

以及它是如何使用的:

final Foo foo = gson.fromJson("{\"foo\":{\"bar\":\"bar\",\"echo\":\"echo\"}}", Foo.class);
System.out.println(foo.foo2);
final String json = gson.toJson(foo);
System.out.println(json);
Run Code Online (Sandbox Code Playgroud)

输出:

回声
{“foo2”:“回声”}

请注意,这种方法有两个缺点:

  • 由于破坏原始信息和 JsonPath 只读语义等根本原因,它不能用于写回原始 JSON。
  • 如果给定的 JSON 文档包含已由同名对象字段映射的对象属性,则下游解析器(由上面的类型适配器使用)具有更高的优先级,因此会导致 JSON 解析错误。