简体   繁体   English

Gson:如何更改序列化中的特定字段键

[英]Gson: How to change specific fields key from serialization

I am using Gson for serialization and I'm struggling with changing field names dynamically. 我正在使用Gson进行序列化,并且正在努力地动态更改字段名称。 Here is my class: 这是我的课:

public class Response<T> 
{

    private String status;
    private String message;
    private T data;

    public Response(T data) 
    {
        this.setData(data);
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }     

}

I have to change the field names dynamically based on the resource. 我必须根据资源动态更改字段名称。 Is there any way to change this? 有什么办法可以改变吗?

Using maps may be not the best choice since your Response class may have special Gson annotations that will be ignored once your response objects are converted to maps. 使用地图可能不是最佳选择,因为您的Response类可能具有特殊的Gson批注,一旦您的响应对象转换为地图,这些批注将被忽略。

Suppose the following simple response class: 假设以下简单响应类:

final class Response<T> {

    @Expose(serialize = true)
    final String status = "STATUS";

    @Expose(serialize = true)
    final String message = "MESSAGE";

    @Expose(serialize = true)
    final T data;

    @Expose(serialize = false, deserialize = false)
    final String whatever = "WHATEVER";

    Response(final T data) {
        this.data = data;
    }

}

This response does not use another Gson annotations for simplicity. 为简单起见,此响应不使用其他Gson注释。 Ad hoc use of a dynamic field renaming: 临时使用动态字段重命名:

final Gson gson = new GsonBuilder()
        .excludeFieldsWithoutExposeAnnotation()
        // ... any Gson configuration here ...
        .create();
final Response<List<String>> response = new Response<>(ImmutableList.of("foo", "bar"));
final JsonElement jsonTree = gson.toJsonTree(response, stringListResponseTypeToken.getType());
final JsonObject responseJson = jsonTree.getAsJsonObject();
final JsonElement dataPropertyJson = responseJson.get("data");
responseJson.remove("data");
responseJson.add(response.getClass().getSimpleName(), dataPropertyJson);
gson.toJson(responseJson, System.out);

Note that the main trick here is creating an intermediate JSON tree and substituting the dynamic property name. 请注意,这里的主要技巧是创建中间JSON树并替换动态属性名称。 Unfortunately, this solution requires an intermediate JSON tree. 不幸的是,此解决方案需要一个中间的JSON树。 Another, more "Gson-ish" solution is creating a special type adapter in order not to re-map the response objects everytime it's necessary. 另一个更“ Gson式”的解决方案是创建一种特殊类型的适配器,以免在需要时不重新映射响应对象。

final Gson gson = new GsonBuilder()
        .excludeFieldsWithoutExposeAnnotation()
        // ... any Gson configuration here ...
        .registerTypeAdapterFactory(getDynamicPropertyResponseTypeAdapterFactory())
        .create();
final Response<List<String>> response = new Response<>(ImmutableList.of("foo", "bar"));
gson.toJson(response, stringListResponseTypeToken.getType(), System.out);

Where the type adapter factory and type adapters are implemented as follows: 类型适配器工厂和类型适配器的实现方式如下:

final class DynamicPropertyResponseTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory dynamicPropertyResponseTypeAdapterFactory = new DynamicPropertyResponseTypeAdapterFactory();

    private DynamicPropertyResponseTypeAdapterFactory() {
    }

    static TypeAdapterFactory getDynamicPropertyResponseTypeAdapterFactory() {
        return dynamicPropertyResponseTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( Response.class.isAssignableFrom(typeToken.getRawType()) ) {
            @SuppressWarnings("unchecked")
            final TypeAdapter<Response<Object>> delegateTypeAdapter = (TypeAdapter<Response<Object>>) gson.getDelegateAdapter(this, typeToken);
            @SuppressWarnings("unchecked")
            final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) getDynamicPropertyResponseJsonTypeAdapter(delegateTypeAdapter, gson);
            return castTypeAdapter;
        }
        return null;
    }

}

Note that this type adapter factory picks the downstream type adapter to avoid infinite recursion if the handled class is Response , and otherwise null is returned to let Gson use its own (de)serialization strategies. 请注意,如果处理的类为Response ,则此类型适配器工厂选择下游类型适配器以避免无限递归,否则返回null ,以让Gson使用其自己的(反)序列化策略。

final class DynamicPropertyResponseJsonTypeAdapter<T>
        extends TypeAdapter<Response<T>> {

    private final TypeAdapter<Response<T>> delegateTypeAdapter;
    private final Gson gson;

    private DynamicPropertyResponseJsonTypeAdapter(final TypeAdapter<Response<T>> delegateTypeAdapter, final Gson gson) {
        this.delegateTypeAdapter = delegateTypeAdapter;
        this.gson = gson;
    }

    static <T> TypeAdapter<Response<T>> getDynamicPropertyResponseJsonTypeAdapter(final TypeAdapter<Response<T>> delegateTypeAdapter, final Gson gson) {
        return new DynamicPropertyResponseJsonTypeAdapter<>(delegateTypeAdapter, gson);
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter out, final Response<T> response)
            throws IOException {
        if ( response == null ) {
            out.nullValue();
            return;
        }
        final JsonElement jsonTree = delegateTypeAdapter.toJsonTree(response);
        final JsonObject responseJson = jsonTree.getAsJsonObject();
        final JsonElement dataPropertyJson = responseJson.get("data");
        responseJson.remove("data");
        responseJson.add(response.getClass().getSimpleName(), dataPropertyJson);
        gson.toJson(responseJson, out);
    }

    @Override
    public Response<T> read(final JsonReader in) {
        throw new UnsupportedOperationException();
    }

}

The same not very cheap trick is used above, but now it works as a part of the Gson instance. 上面使用了同样便宜的技巧,但现在它已成为Gson实例的一部分。 For both cases the output is as follows: 对于这两种情况,输出如下:

{"status":"STATUS","message":"MESSAGE","Response":["foo","bar"]} {“ status”:“ STATUS”,“ message”:“ MESSAGE”,“ Response”:[“ foo”,“ bar”]}

Another options you might want to take into account are: 您可能要考虑的另一个选项是:

  • Making the Response class abstract and letting the child classes to define their own data field names via @SerializedName if the names are supposed to be hard-coded. 如果应该对名称进行硬编码,则使Response类成为抽象类,并让子类通过@SerializedName定义自己的data字段名称。
  • Creating your implementation ReflectiveTypeAdapterFactory (see the Gson source code) and making the fields names dynamic without creating an intemediate JSON trees. 创建实现ReflectiveTypeAdapterFactory (请参阅Gson源代码),并使字段名称动态化,而无需创建中间JSON树。

If you need to change the field's name than it means you don't need type safety, so the following will do: 如果您需要更改字段名称,则意味着您不需要类型安全,因此可以执行以下操作:

Map<String, Object> response = new LinkedHashMap<>();
response.put("message", message);
response.put("status", status);
response.put(dynamicFieldName, dynamicFieldValue);
String json = gson.toJson(response);

You can still wrap a convenience library on top of this low-level code to account for common use cases. 您仍然可以在此低级代码之上包装一个便捷库,以解决常见的用例。

Why not use HashMap for such cases? 在这种情况下为什么不使用HashMap?

private HashMap<String, String> data;

public HashMap<String, String> getData() {
    return this.data;
}

public void setDataValue(String key, String value) {
    data.put(key, value);
}

Note that the data field can be a HashMap of <String, Object> or <Long, Object> too, to save sub-objects, thus accepting all kind of Gson supported structures. 请注意,数据字段也可以是<String, Object><Long, Object>的HashMap,以保存子对象,从而接受所有Gson支持的结构。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM