Why GSON fails to convert Object when it's a field of another Object? [duplicate]

故事扮演 提交于 2021-01-29 15:34:48

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!