【问题标题】:Gson generic deserialize JSONGson 通用反序列化 JSON
【发布时间】:2021-05-18 16:35:42
【问题描述】:

我想使用 Gson 构建一个 websocket 客户端。 正如预期的那样,websocket 通信基于几个不同的消息。

我想要一种优雅的方式将 JSON 字符串转换为声明的模型/对象。

第一次尝试:

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

第二次尝试,我看到了通用类型和任意类型的解决方案,但与第一次尝试非常相似。我需要声明类型,而不是查看区分类。这在书面代码中非常相似。

我认为更容易拥有和维护的方法是在某个包中声明消息的模型并拥有其中一种实现。

  1. 使用基类/接口作为fromJson 的公共基础
  2. 在某处注册支持的模型,这样我就可以像gson.fromJson(messageString)一样简单地执行解析器

有这样或其他的方法来实现更优雅的解决方案吗?

【问题讨论】:

    标签: java json gson


    【解决方案1】:

    我认为通用解决方案不会有效,因为“包含”操作需要线性复杂性搜索(与使用鉴别器类型字段的 RuntimeTypeAdapterFactory 不同),但它可能会像这样实现:

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

    下面的例子:

    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();
    

    我不确定这个实现是否超级优化(好吧,我放弃,不是),但我猜下面的实现会更简单更高效(不管没有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();
    

    【讨论】:

    • 感谢您的详细回复。但是,与一组 if..else 表达式相比,您的解决方案似乎使实现更冗长且更难维护。但看看我的问题,我可能会要求一些不可能或难以实现的东西。
    • @PauloOliveira 是的,Gson 只是一个基础,你能做的最多的就是实现一个通用解决方案(并可能与其他人共享)或使用一个临时解决方案(可能使用一个代码生成器)。如果您希望它“不那么冗长”,您可以牺牲一些性能并注册第一个示例中的规则,仍然让它集中并在反序列化级别完成(隐藏所有的冗长)。除非您有代码生成器,否则手动 if/else-s 更难维护。
    猜你喜欢
    • 1970-01-01
    • 2017-01-27
    • 1970-01-01
    • 1970-01-01
    • 2015-07-11
    • 1970-01-01
    相关资源
    最近更新 更多