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.
fromJson
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.