简体   繁体   中英

Gson generic deserialize JSON

I want to build a websocket client using Gson. As expected a websocket communication is based on several different messages.

I would like to have an elegant way to convert an JSON string to an declared model/object.

First attempt:

if message.contains("fieldA") {
    return gson.fromJson(message, ObjectA.class);
} else if message.contains("fieldB") {
    return gson.fromJson(message, ObjectB.class);
} else if message.contains("fieldC") {
    return gson.fromJson(message, ObjectC.class);
}
// etc

Second attempt, I've seen solutions with Generic and Arbitrary Types but is very similar to the first attempt. Instead of looking of the differentiating the Class I need to declare the Type. Which in written code is pretty similar.

What I think would be easier to have and maintain, would be to declare the models for the messages in some package and have one of these implementations.

  1. Use a base Class/interface as a common ground for the fromJson
  2. Register supported models somewhere so I could execute the parser simple as gson.fromJson(messageString)

There is a way like this or other to achieve a more elegant solution?

I don't think a generic solution would be efficient due to linear complexity search required for the "contains" operation (unlike RuntimeTypeAdapterFactory that uses a discriminator type field), but it might be implemented like this:

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class JsonElementTypeAdapterFactoryBuilder<V, J extends JsonElement> {

    private final Class<V> baseType;

    private final Collection<Rule<V, J>> rules = new ArrayList<>();

    public static <V, J extends JsonElement> JsonElementTypeAdapterFactoryBuilder<V, J> construct(final Class<V> baseType) {
        return new JsonElementTypeAdapterFactoryBuilder<>(baseType);
    }

    public JsonElementTypeAdapterFactoryBuilder<V, J> register(final Predicate<? super J> isSupported,
            final Supplier<? extends TypeToken<? extends V>> getTypeToken) {
        rules.add(new Rule<V, J>(isSupported, getTypeToken));
        return this;
    }

    public TypeAdapterFactory build() {
        @SuppressWarnings("unchecked")
        final Rule<V, J>[] rules = this.rules.stream()
                .toArray(Rule[]::new);
        return new TypeAdapterFactory() {
            @Override
            @Nullable
            public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
                if ( !baseType.isAssignableFrom(typeToken.getRawType()) ) {
                    return null;
                }
                final TypeAdapterFactory typeAdapterFactory = this;
                return new TypeAdapter<T>() {
                    private final Map<TypeToken<? extends V>, TypeAdapter<? extends V>> index = new ConcurrentHashMap<>();

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

                    @Override
                    public T read(final JsonReader in) {
                        @SuppressWarnings("unchecked")
                        final J jsonElement = (J) Streams.parse(in);
                        for ( final Rule<V, J> rule : rules ) {
                            if ( rule.isSupported.test(jsonElement) ) {
                                final TypeToken<? extends V> concreteTypeToken = rule.getTypeToken.get();
                                final TypeAdapter<? extends V> typeAdapter = index.computeIfAbsent(concreteTypeToken, tt -> gson.getDelegateAdapter(typeAdapterFactory, tt));
                                @SuppressWarnings("unchecked")
                                final T value = (T) typeAdapter.fromJsonTree(jsonElement);
                                return value;
                            }
                        }
                        throw new AssertionError(typeToken);
                    }
                };
            }
        };
    }

    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    private static final class Rule<V, J extends JsonElement> {

        private final Predicate<? super J> isSupported;
        private final Supplier<? extends TypeToken<? extends V>> getTypeToken;

    }

}

with the following example:

private static final Gson gson = new GsonBuilder()
        .disableHtmlEscaping()
        .disableInnerClassSerialization()
        .registerTypeAdapterFactory(JsonElementTypeAdapterFactoryBuilder.<ICommon, JsonObject>construct(ICommon.class)
                .register(jsonObject -> jsonObject.has("fieldA"), () -> objectATypeToken)
                .register(jsonObject -> jsonObject.has("fieldB"), () -> objectBTypeToken)
                .register(jsonObject -> jsonObject.has("fieldC"), () -> objectCTypeToken)
                .build()
        )
        .create();

I'm not sure whether this implementation is super-optimized (all right, I give up, it is not), but I guess that the following implementation would be both simpler and more efficient (regardless no API):

private static final Gson gson = new GsonBuilder()
        .disableHtmlEscaping()
        .disableInnerClassSerialization()
        .registerTypeAdapterFactory(new TypeAdapterFactory() {
            @Override
            @Nullable
            public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
                if ( !ICommon.class.isAssignableFrom(typeToken.getRawType()) ) {
                    return null;
                }
                final TypeAdapter<ObjectA> aTypeAdapter = gson.getDelegateAdapter(this, new TypeToken<ObjectA>() {});
                final TypeAdapter<ObjectB> bTypeAdapter = gson.getDelegateAdapter(this, new TypeToken<ObjectB>() {});
                final TypeAdapter<ObjectC> cTypeAdapter = gson.getDelegateAdapter(this, new TypeToken<ObjectC>() {});
                return new TypeAdapter<T>() {
                    @Override
                    public void write(final JsonWriter out, final T value) {
                        throw new UnsupportedOperationException("TODO");
                    }

                    @Override
                    public T read(final JsonReader in)
                            throws IOException {
                        final JsonElement jsonElement = Streams.parse(in);
                        final JsonObject jsonObject = jsonElement
                                .getAsJsonObject();
                        final TypeAdapter<? extends ICommon> typeAdapter;
                        if ( jsonObject.has("fieldA") ) {
                            typeAdapter = aTypeAdapter;
                        } else if ( jsonObject.has("fieldB") ) {
                            typeAdapter = bTypeAdapter;
                        } else if ( jsonObject.has("fieldC") ) {
                            typeAdapter = cTypeAdapter;
                        } else {
                            throw new JsonParseException("Unsupported JSON element: " + jsonElement);
                        }
                        @SuppressWarnings("unchecked")
                        final T value = (T) typeAdapter.fromJsonTree(jsonElement);
                        return value;
                    }
                }
                        .nullSafe();
            }
        })
        .create();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM