如何序列化一个JSON对象的孩子到一个字段? [英] How to serialize a JSON object child into a field?

查看:78
本文介绍了如何序列化一个JSON对象的孩子到一个字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  {
foo:{
bar:bar,
echo:echo
}
}

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

class Foo {
public String foo2;
}

我想序列化 echo 直接转换为 foo 。是这样的可能:
$ b

  class Foo {
@SerializedName(foo /回声)
公共字符串foo2;
}

或者我可以如何使用自定义的反序列化器来做到这一点?

解决方案

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

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

  final class Foo {

//或@JsonPathExpression(foo.echo)
@JsonPathExpression ($ .foo.echo)
字符串foo2;

$ b

@JsonPathExpression 是自定义注释,它可以自己处理( JsonPath 可能是一个较短的名称,但它已被JsonPath库占用,因此不会混淆) p>

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

字符串值();






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

  final类JsonPathTypeAdapterFactory 
实现TypeAdapterFactory {

//类型适配器工厂是无状态的,因此可以实例化一次
private static final TypeAdapterFactory jsonPathTypeAdapterFactory =新的JsonPathTypeAdapterFactory();

private JsonPathTypeAdapterFactory(){
}

static TypeAdapterFactory getJsonPathTypeAdapterFactory(){
return jsonPathTypeAdapterFactory;
}

@Override
public< T> TypeAdapter< T> create(final Gson gson,final TypeToken< T> typeToken){
//拾取下游类型适配器以避免无限递归
final TypeAdapter< T> delegateAdapter = gson.getDelegateAdapter(this,typeToken);
//收集@ JsonPathExpression - 带注释的字段
最终集合< FieldInfo> fieldInfos = FieldInfo.of(typeToken.getRawType());
//如果找不到这样的字段,那么只需返回委托类型适配器
//否则包装类型适配器以进行一些注释处理
return fieldInfos.isEmpty()
? delegateAdapter
:new JsonPathTypeAdapter<>(gson,delegateAdapter,gson.getAdapter(JsonElement.class),fieldInfos);
}

private static final class JsonPathTypeAdapter< T>
扩展了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只能通过表达式读取,但不能用表达式写,所以我们只能写它,因为它是...
delegateAdapter.write(out,value);
}

@Override
public T read(final JsonReader in)
throws IOException {
//构建原始JSON树以保留* all *字段
final JsonElement outerJsonElement = jsonElementTypeAdapter.read(in).getAsJsonObject();
//反序列化该值,不存在的字段将被忽略
final T value = delegateAdapter.fromJsonTree(outerJsonElement);
(final FieldInfo fieldInfo:fieldInfos){
try {
//通过JSON路径表达式解析JSON元素
final JsonElement innerJsonElement = fieldInfo.jsonPath.read(outerJsonElement);
//并将其转换为字段类型
final Object innerValue = gson.fromJson(innerJsonElement,fieldInfo.field.getType());
//从现在开始,它可以分配给对象字段...
fieldInfo.field.set(value,innerValue);
} catch(最终PathNotFoundException被忽略){
//如果没有给出路径,那么就忽略赋值给字段
} catch(final IllegalAccessException ex){
throw new IOException (前);
}
}
返回值;
}

}

private static final FieldInfo {

private final Field field;
private final JsonPath jsonPath;

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

//扫描JsonPathExpressionAnnotation的给定类
private static Collection< FieldInfo> (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())));
}
}
返回集合;
}

}

}



<现在,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);
}
});

}

以及如何使用它:

  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);

输出:


echo
{foo2:echo}

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


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


I have a JSON object that looks like this

{
  "foo":{
      "bar":"bar",
      "echo":"echo"
  }
}

But then my Java object looks like this:

class Foo {
    public String foo2;
}

I would like to serialize echo directly into foo. Is something like this possible:

class Foo {
    @SerializedName("foo/echo")
    public String foo2;
}

Or how can I do this with a custom deserializer?

解决方案

As an alternative approach you could also create your own type adapter in order to apply JSON expressions to the not existing fields. It could be based on JsonPath if you are free to add new libraries to the project you're working on.

Having such a non-standard type adapter, you could omit an intermediate mapping class binding directly to the missing field:

final class Foo {

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

}

@JsonPathExpression is a custom annotation and it can be processed yourself (JsonPath could be a shorter name but it's already occupied by the JsonPath library so not to make confusions):

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

    String value();

}

Type adapters allow to write complicated serialization/deserialization strategies, and one of their features is that they can be combined to write post-processors, so, for example, custom annotations could be processed.

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

    }

}

Now both Gson and JsonPath must be configured (the latter does not use Gson by default):

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

}

And how it's used:

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);

Output:

echo
{"foo2":"echo"}

Note that this approach has two disadvantages:

  • It cannot be used to write the original JSON back due to fundamental reasons like destroying the original information and JsonPath read-only semantics.
  • If a given JSON document contains an object property that's already mapped by the same-name object field, the downstream parser (used by the type adapter above) has higher priority therefore causing JSON parsing errors.

这篇关于如何序列化一个JSON对象的孩子到一个字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆