问题
Assume that:
- there is a bunch of Java objects that you need to pass - for example - to some API
- you are not willing or can not change declaration for those objects
- unfortunately API requires something that is not declared in those objects
As an example (inspired by this question) there is a simple class:
@Getter
@RequiredArgsConstructor
public class Login {
private final String username, password;
}
However, API expects JSON like:
{
"username": "uname",
"password": "pword",
"version": 1
}
and this same problem applies to all other 99 classes: those need also field version with value 1 in serialized JSON.
There are some solutions that require either low level string manipulation or adding lots of boilerplate code but what would be the generic way to deal with this issue with GSON?
回答1:
Firstly, to have Gson to serialize (or to deserialize) bunch of different types of objects having same type of adapting the best way to avoid registering lots of adapters or altering class declarations is to make benefit of TypeAdapterFactory.
It is not itself bound to any Type or Class but decides per type which TypeAdapter to return when Gson bumps into some object to serialize (or deserialize). Using TypeAdaterFactory frees code from registering lots of TypeAdapters.
Secondly and naturally, to avoid creating many TypeAdapters the solution is to make a generic type of TypeAdapter whenever possible.
Starting from the generic TypeAdapter in questions case it might be like:
@RequiredArgsConstructor
private class GenericTypeAdapter<T> extends TypeAdapter<T> {
// typeToken is needed when deserializing
private final TypeToken<T> typeToken;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Override
public void write(JsonWriter out, T value) throws IOException {
// Altering could be done with some low level string manipulation
// but JsonObject makes altering object more safe.
// Feel free to comment for better way to instantiate it,
// this is just for an example.
JsonObject jsonObject = gson.fromJson(gson.toJson(value)
,JsonElement.class).getAsJsonObject();
// alter jsonObject in any way needed,
// here is only added version information
jsonObject.addProperty("version", 1);
out.jsonValue(gson.toJson(jsonObject));
}
@Override
public T read(JsonReader in) throws IOException {
// maybe needless to mention but mention still:
// here it is possible to init object in a way
// that provided JSON solely does not make possible
return gson.fromJson(in, typeToken.getType());
}
}
Then the TypeAdapterFactory. It is quite simple but pay attention to the comments in example code. As mentioned before TypeAdapterFactory is responsible of returning correct TypeAdapter per object. Although it is meant to apply to a bunch of types it might not be meant to apply to all the types. Most simple TypeAdapterFactory:
public class GenericTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// here should be checked if the passed type needs any custom
// adapter and if it needs then decide which adapter to return
// or in case of no customization needed return null for default
// adapter.
// decision can be made for example by
// * type itself
// * package name
// * if type implements / extends some super type
return new GenericTypeAdapter<>(type);
}
}
Usage would then simply be:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new GenericTypeAdapterFactory())
.setPrettyPrinting()
.create()
Note: I prepared this answer originally to this question but since it later appeared to be Kotlin(?) based I felt better to create a more generic Q&A dealt with Java.
来源:https://stackoverflow.com/questions/53209467/how-to-change-resulting-json-when-serializing-more-or-less-globally-if-class-d