问题
GSON fails to convert Errorneous
to JSON properly when it's inside of other Object
.
But it works well when it's converted as a top level object. Why, and how to fix it?
Example:
import com.google.gson.GsonBuilder
sealed class Errorneous<R> {}
data class Success<R>(val result: R) : Errorneous<R>()
data class Fail<R>(val error: String) : Errorneous<R>()
class Container(val value: Errorneous<String>)
fun main() {
print(GsonBuilder().create().toJson(Container(Fail("some error"))))
print(GsonBuilder().create().toJson(Fail<String>("some error")))
}
Output
{"value":{}}
{"error":"some error"}
But it should be
{"value":{"error":"some error"}}
{"error":"some error"}
回答1:
I made some comments regarding Gson behavior right under the post (in short: not enough runtime type information), so this is only code to make it work and make it actual type-aware.
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Class<? super T> rawType = typeToken.getRawType();
if ( rawType != Errorneous.class ) {
return null;
}
final ParameterizedType parameterizedType = (ParameterizedType) typeToken.getType();
@SuppressWarnings("unchecked")
final TypeToken<Success<?>> successTypeToken = (TypeToken<Success<?>>) TypeToken.getParameterized(Success.class, parameterizedType.getActualTypeArguments());
@SuppressWarnings("unchecked")
final TypeToken<Fail<?>> failTypeToken = (TypeToken<Fail<?>>) TypeToken.getParameterized(Fail.class, parameterizedType.getActualTypeArguments());
final TypeAdapter<Success<?>> successTypeAdapter = gson.getDelegateAdapter(this, successTypeToken);
final TypeAdapter<Fail<?>> failTypeAdapter = gson.getDelegateAdapter(this, failTypeToken);
final TypeAdapter<Errorneous<?>> concreteTypeAdapter = new TypeAdapter<Errorneous<?>>() {
@Override
public void write(final JsonWriter out, final Errorneous<?> value)
throws IOException {
if ( value instanceof Success ) {
final Success<?> success = (Success<?>) value;
successTypeAdapter.write(out, success);
return;
}
if ( value instanceof Fail ) {
final Fail<?> fail = (Fail<?>) value;
failTypeAdapter.write(out, fail);
return;
}
throw new AssertionError(); // even null cannot get here: it is protected with .nullSafe() below
}
@Override
public Errorneous<?> read(final JsonReader in) {
throw new UnsupportedOperationException();
}
};
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = ((TypeAdapter<T>) concreteTypeAdapter)
.nullSafe();
return typeAdapter;
}
})
.create();
@AllArgsConstructor
@SuppressWarnings("unused")
private abstract static class Errorneous<R> {
}
@AllArgsConstructor
@SuppressWarnings("unused")
private static final class Success<R>
extends Errorneous<R> {
private final R result;
}
@AllArgsConstructor
@SuppressWarnings("unused")
private static final class Fail<R>
extends Errorneous<R> {
private final String error;
}
@AllArgsConstructor
@SuppressWarnings("unused")
private static class Container {
private final Errorneous<String> value;
}
public static void main(final String... args) {
System.out.println(gson.toJson(new Container(new Fail<>("some error"))));
System.out.println(gson.toJson(new Fail<>("some error")));
}
As you can see, the type adapter factory first resolves type adapters for both Success
and Fail
, and then picks a proper one based on the actual class of the Errorneous
value with instanceof
().
Here is what it prints:
{"value":{"error":"some error"}}
{"error":"some error"}
The deserialization is made an unsupported operation since it must decide how the JSON can be deserialized: 1) either on a type designator field (see RuntimeTypeAdapterFactory
in Gson extras in their repository on GitHub; it's not bundled and published as an artifact); 2) or analyze the structure of the object making heuristics analysis (much harder to implement and may face with ambiguous cases).
I don't do Kotlin, but the Java code above can be probably easily converted to its Kotlin counterpart right in IntelliJ IDEA.
回答2:
Answer in Kotlin copied from the similar Java Question
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
sealed class Errorneous<R> {}
data class Success<R>(val result: R) : Errorneous<R>()
data class Fail<R>(val error: String) : Errorneous<R>()
class Container(
val value: Errorneous<String>
)
fun main() {
val builder = GsonBuilder()
builder.registerTypeAdapter(
Errorneous::class.java, ErrorneousSerializer()
)
val gson = builder.create()
print(gson.toJson(Container(Fail("some error"))))
print(gson.toJson(Fail<String>("some error")))
}
class ErrorneousSerializer : JsonSerializer<Errorneous<Any>> {
override fun serialize(
o: Errorneous<Any>, type: Type, ctx: JsonSerializationContext
): JsonElement {
return ctx.serialize(o as Any)
}
}
来源:https://stackoverflow.com/questions/65918374/why-gson-fails-to-convert-object-when-its-a-field-of-another-object