The same field has two different types, which creates problems with the Gson converter for Retrofit 2

Here is the JSON schema:

enter image description here

As you can see, a rating can be either logical or objective.

I use Retrofit 2 and Gson Converter. How do I create my model for this circuit?

+6
source share
3 answers

Here is how I solved this problem:

Create a custom type adapter in your model and evaluate manually;

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 ;
        }
    }

}

Here you create your gson using this custom adapter:

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

Add it to modify as follows:

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

TADADADADADADADADAD!

+3
source

, . , , "" , , :

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" , .

+1

Gson , factory , Gson () . , , AccountState ReflectiveTypeAdapterFactory ReflectiveTypeAdapterFactory.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 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 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);
}

:



true

, JsonSerializer JsonDeserializer - ( 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 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?).

0
source

Source: https://habr.com/ru/post/1671813/


All Articles