相同的字段有两种不同的类型给改造2的Gson转换器带来麻烦

时间:2017-03-08 14:12:14

标签: android gson retrofit2

这是json架构:

enter image description here

如您所见,rating可以是boolean和object。

我正在使用Retrofit 2和Gson转换器。我该如何为这个模式创建我的模型?

3 个答案:

答案 0 :(得分:3)

以下是我解决这个问题的方法:

在模型中创建自定义类型适配器并手动解析;

public class AccountState {

    //@SerializedName("rated") //NOPE, parse it manually
    private Integer mRated; //also don't name it rated


    public Integer getRated() {
        return mRated;
    }

    public void setRated(Integer rated) {
        this.mRated = rated;
    }


    public static class AccountStateDeserializer implements JsonDeserializer<AccountState> {

        @Override
        public AccountState deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            AccountState accountState = new Gson().fromJson(json, AccountState.class);
            JsonObject jsonObject = json.getAsJsonObject();

            if (jsonObject.has("rated")) {
                JsonElement elem = jsonObject.get("rated");
                if (elem != null && !elem.isJsonNull()) {
                    if(elem.isJsonPrimitive()){
                        accountState.setRated(null);
                    }else{
                        accountState.setRated(elem.getAsJsonObject().get("value").getAsInt());
                    }
                }
            }
            return accountState ;
        }
    }

}

在这里,您可以使用此自定义适配器创建您的gson:

final static Gson gson = new GsonBuilder()
            .registerTypeAdapter(AccountState.class, new AccountState.AccountStateDeserializer())
            .create();

将其添加到改装中:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.ENDPOINT)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(okHttpClient)
                .build();

TADADADADADADADDAD!

答案 1 :(得分:2)

您无需实现自定义转换器即可使其正常工作。 您所要做的就是为变量添加一个通用的“对象”类型,然后通过执行此操作来检查它是哪种数据类型:

if(object.getClass == YourClass.class){
  Whatever we = ((YourClass) object).getWhatever();
} else if(object.getClass == YourOtherClass.class){
  String name = ((YourOtherClass) object).getName();
}

您可以根据需要为此变量添加任意数量的数据类型。 您还可以使用java类型“​​String.class”,“Boolean.class”或任何您喜欢的类型。

答案 2 :(得分:0)

Gson有一个很好的功能,允许将自定义类型适配器或类型适配器工厂注入某个字段,因此让Gson管理主机对象和后者的字段(反)序列化。因此,您可以确保AccountState仍然可以使用ReflectiveTypeAdapterFactoryReflectiveTypeAdapterFactory.Adapter进行反序列化,因此可以应用GsonBuilder中定义的所有反序列化策略。

final class AccountState {

    // This is what can make life easier. Note its advantages:
    // * PackedBooleanTypeAdapterFactory can be reused multiple times
    // * AccountState life-cycle can be managed by Gson itself,
    //   so it can manage *very* complex deserialization automatically.
    @JsonAdapter(PackedBooleanTypeAdapterFactory.class)
    final Boolean rated = null;

}

接下来,如何实施PackageBooleanTypeAdapterFactory

final class PackedBooleanTypeAdapterFactory
        implements TypeAdapterFactory {

    // Gson can instantiate this itself, no need to expose
    private PackedBooleanTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Check if it's the type we can handle ourself
        if ( typeToken.getRawType() == Boolean.class ) {
            final TypeAdapter<Boolean> typeAdapter = new PackedIntegerTypeAdapter(gson);
            // Some Java "unchecked" boilerplate here...
            @SuppressWarnings("unchecked")
            final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
            return castTypeAdapter;
        }
        // If it's something else, let Gson pick a downstream type adapter on its own
        return null;
    }

    private static final class PackedIntegerTypeAdapter
            extends TypeAdapter<Boolean> {

        private final Gson gson;

        private PackedIntegerTypeAdapter(final Gson gson) {
            this.gson = gson;
        }

        @Override
        public void write(final JsonWriter out, final Boolean value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Boolean read(final JsonReader in)
                throws MalformedJsonException {
            // Pick next token as a JsonElement
            final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
            // Note that Gson uses JsonNull singleton to denote a null
            if ( jsonElement.isJsonNull() ) {
                return null;
            }
            if ( jsonElement.isJsonPrimitive() ) {
                return jsonElement
                        .getAsJsonPrimitive()
                        .getAsBoolean();
            }
            if ( jsonElement.isJsonObject() ) {
                return jsonElement
                        .getAsJsonObject()
                        .getAsJsonPrimitive("value")
                        .getAsBoolean();
            }
            // Not something we can handle
            throw new MalformedJsonException("Cannot parse: " + jsonElement);
        }

    }

}

演示:

public static void main(final String... args) {
    parseAndDump("{\"rated\":null}");
    parseAndDump("{\"rated\":true}");
    parseAndDump("{\"rated\":{\"value\":true}}");
}

private static void parseAndDump(final String json) {
    final AccountState accountState = gson.fromJson(json, AccountState.class);
    System.out.println(accountState.rated);
}

输出:

  


  真正
  真的

请注意,JsonSerializerJsonDeserializer由于其树模型设计而具有一些性能和内存成本(只要它们在内存中,您就可以轻松遍历JSON树)。有时,对于简单的情况,流式类型适配器可能更可取。优点:消耗更少的内存并且工作更快。缺点:难以实施。

final class AccountState {

    @JsonAdapter(PackedBooleanTypeAdapter.class)
    final Boolean rated = null;

}

请注意,rated字段直接接受类型适配器,因为它不需要Gson个实例来构建JSON树(JsonElement s)。

final class PackedBooleanTypeAdapter
        extends TypeAdapter<Boolean> {

    // Gson still can instantiate this type adapter itself  
    private PackedBooleanTypeAdapter() {
    }

    @Override
    public void write(final JsonWriter out, final Boolean value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Boolean read(final JsonReader in)
            throws IOException {
        // Peeking the next JSON token and dispatching parsing according to the given token
        final JsonToken token = in.peek();
        switch ( token ) {
        case NULL:
            return parseAsNull(in);
        case BOOLEAN:
            return parseAsBoolean(in);
        case BEGIN_OBJECT:
            return parseAsObject(in);
        // The below might be omitted, since some code styles prefer all switch/enum constants explicitly
        case BEGIN_ARRAY:
        case END_ARRAY:
        case END_OBJECT:
        case NAME:
        case STRING:
        case NUMBER:
        case END_DOCUMENT:
            throw new MalformedJsonException("Cannot parse: " + token);
        // Not a known token, and must never happen -- something new in a newer Gson version?
        default:
            throw new AssertionError(token);
        }

    }

    private Boolean parseAsNull(final JsonReader in)
            throws IOException {
        // null token still has to be consumed from the reader
        in.nextNull();
        return null;
    }

    private Boolean parseAsBoolean(final JsonReader in)
            throws IOException {
        // Consume a boolean value from the reader
        return in.nextBoolean();
    }

    private Boolean parseAsObject(final JsonReader in)
            throws IOException {
        // Consume the begin object token `{`
        in.beginObject();
        // Get the next property name
        final String property = in.nextName();
        // Not a value? Then probably it's not what we're expecting for
        if ( !property.equals("value") ) {
            throw new MalformedJsonException("Unexpected property: " + property);
        }
        // Assuming the property "value" value must be a boolean
        final boolean value = in.nextBoolean();
        // Consume the object end token `}`
        in.endObject();
        return value;
    }

}

这个应该更快。输出保持不变。请注意,Gson在两种情况下都不需要GsonBuilder。至于我记得Retrofit 2是如何工作的,GsonConverterFactory仍然是必需的(不确定,Gson不是Retrofit 2中的默认序列化器吗?)。