【问题标题】:JSON query filter transportJSON 查询过滤器传输
【发布时间】:2017-03-27 12:19:44
【问题描述】:

我想将 WHERE 子句作为 JSON 从客户端传输到服务器。 我在服务器上创建了FilterInfo.classFilter.class

   public class Filter<T> {
      private String fieldName;
      private String operand;
      private T value; 
   }

   public class FilterInfo {
     private List<Filter> filters = new ArrayList<Filter>();
     private String orderBy;
   }

我的filterInfo JSON 示例:

{
  "filters": [
    { "fieldName" : "Name",
      "operand" : "=",
      "value" : "John" },

    { "fieldName" : "Age",
      "operand"  : ">=",
      "value"  : "30" }

  ],
  "orderBy": "Age"
}

那么在服务器上读取这个 JSON 并构建查询应该会很棒。

Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .setDateFormat(Constants.MY_DATE_FORMAT)
                .create();
FilterInfo filterInfo = gson.fromJson(jsonString, FilterInfo.class);

不幸的是,DateInteger 值反序列化为 StringDouble

我看过TypeToken、自定义序列化器/反序列化器的示例,但不知道如何将它们应用到我身上。

如果您能找出我的错误并提出好主意,我会很高兴。 谢谢!

【问题讨论】:

    标签: java json generics gson


    【解决方案1】:

    不幸的是,Date 和 Integer 值反序列化为 String 和 Double。

    当您定义像Field&lt;T&gt; 这样的泛型类型字段时——Gson 无法获得足够的信息来说明如何将某个值反序列化为某个类型。这是一个基本的限制:没有类型信息。因此 Gson 解析 &lt;T&gt; 就好像它被参数化为 &lt;Object&gt;。当某个目标“槽”(列表元素、对象字段等)被认为是java.lang.Object 时,Gson 根据值文字的类型解析一个 JSON 值:如果它类似于"...",那么它可能是一个@987654326 @;如果它类似于0,那么它肯定是Number,并且更准确:Double(双精度值是最大的标准数值——Gson 只是节省了数字类型检测和解析的时间+用户代码应该有@ 987654330@ 并使用instanceof 检测特定的列表元素——它可能是整数、长整数或双精度值——使用起来不太方便,因此java.lang.Double 是默认策略)。所以你有字符串和双精度而不是日期和整数:Gson simple 不能有你想要的类型信息本身

    为什么不能直接使用类型标记:类型标记用于为相同类型的元素指定类型参数,因此即使对于两个元素,也不能有多个类型标记来覆盖不同类型列表(列表类型标记定义所有列表元素的类型)。

    要完成您需要的工作,您可以创建一个类型适配器和一个相应的类型适配器工厂来执行某种查找来解析具体类型。说,

    final class FilterTypeAdapterFactory
            implements TypeAdapterFactory {
    
        // This is a strategy up to your needs: resolve a java.lang.reflect.Type by a filter object content 
        private final Function<? super JsonObject, ? extends Type> typeResolvingStrategy;
    
        private FilterTypeAdapterFactory(final Function<? super JsonObject, ? extends Type> typeResolvingStrategy) {
            this.typeResolvingStrategy = typeResolvingStrategy;
        }
    
        static TypeAdapterFactory getFilterTypeAdapterFactory(final Function<? super JsonObject, ? extends Type> typeResolvingStrategy) {
            return new FilterTypeAdapterFactory(typeResolvingStrategy);
        }
    
        @Override
        public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
            // Is it the Filter class?
            if ( Filter.class.isAssignableFrom(typeToken.getRawType()) ) {
                // Get the JsonObject type adapter
                final TypeAdapter<JsonObject> jsonObjectTypeAdapter = gson.getAdapter(JsonObject.class);
                // This is a function to resolve a downstream type adapter by the given type
                // If a downstream parser is not used, then the lookup will end up with self-recursion...
                final Function<Type, TypeAdapter<T>> typeTypeAdapterFunction = type -> {
                    // Create a type token dynamically
                    @SuppressWarnings("unchecked")
                    final TypeToken<T> delegateTypeToken = (TypeToken<T>) TypeToken.get(type);
                    // And get the downstream type adapter
                    return gson.getDelegateAdapter(this, delegateTypeToken);
                };
                return new FilterTypeAdapter<>(jsonObjectTypeAdapter, typeTypeAdapterFunction, typeResolvingStrategy);
            }
            // Not a thing we can handle? Return null, and Gson will try to perform lookup itself
            return null;
        }
    
        private static final class FilterTypeAdapter<T>
                extends TypeAdapter<T> {
    
            private final TypeAdapter<JsonObject> jsonObjectTypeAdapter;
            private final Function<? super Type, ? extends TypeAdapter<T>> typeAdapterResolver;
            private final Function<? super JsonObject, ? extends Type> typeResolvingStrategy;
    
            private FilterTypeAdapter(
                    final TypeAdapter<JsonObject> jsonObjectTypeAdapter,
                    final Function<? super Type, ? extends TypeAdapter<T>> typeAdapterResolver,
                    final Function<? super JsonObject, ? extends Type> typeResolvingStrategy
            ) {
                this.jsonObjectTypeAdapter = jsonObjectTypeAdapter;
                this.typeAdapterResolver = typeAdapterResolver;
                this.typeResolvingStrategy = typeResolvingStrategy;
            }
    
            @Override
            public void write(final JsonWriter out, final T value) {
                // If you ever need it, then you have to implement it
                throw new UnsupportedOperationException();
            }
    
            @Override
            public T read(final JsonReader in)
                    throws IOException {
                // Read the next {...} and convert it to JsonObject
                final JsonObject jsonObject = jsonObjectTypeAdapter.read(in);
                // Now resolve a real type by the given JsonObject instance
                // ... and resolve its type adapter
                final TypeAdapter<T> delegateTypeAdapter = typeAdapterResolver.apply(typeResolvingStrategy.apply(jsonObject));
                // Since the reader has the {...} value already consumed, we cannot read it at this moment
                // But we can convert the cached JsonObject to the target type object
                return delegateTypeAdapter.fromJsonTree(jsonObject);
            }
    
        }
    
    }
    

    好的,如何使用?我已经使用以下映射对其进行了测试:

    final class Filter<T> {
    
        final String fieldName = null;
        final String operand = null;
        final T value = null;
    
    }
    
    final class FilterInfo {
    
        final List<Filter<?>> filters = null;
        final String orderBy = null;
    
    }
    

    JSON 内类型名称策略

    如果您可以在 JSON 中提供类型名称来查找过滤器类型,那么示例 JSON 可能如下所示:

    {
        "filters": [
            {"_type": "date", "fieldName": "fooDate", "operand": "=", "value": "1997-12-20"},
            {"_type": "int", "fieldName": "barInteger", "operand": ">=", "value": 10}
        ],
        "orderBy": "fooDate"
    }
    

    现在Gson 实例可以这样构建:

    private static final Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd")
            .registerTypeAdapterFactory(getFilterTypeAdapterFactory(jsonObject -> {
                if ( !jsonObject.has("_type") ) {
                    return defaultFilterType;
                }
                switch ( jsonObject.get("_type").getAsString() ) {
                case "int":
                    return integerFilterType;
                case "date":
                    return dateFilterType;
                default:
                    return defaultFilterType;
                }
            }))
            .create();
    

    替代策略

    如果您不想增强您的 JSON 文档(这很好),那么您可以替换该策略,但是由于多种原因解析类型可能会更加复杂,因为它在很大程度上取决于给定的过滤器值名称 (相同的名称可能用于不同的类型):

    {
        "filters": [
            {"fieldName": "fooDate", "operand": "=", "value": "1997-12-20"},
            {"fieldName": "barInteger", "operand": ">=", "value": 10}
        ],
        "orderBy": "fooDate"
    }
    
    private static final Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd")
            .registerTypeAdapterFactory(getFilterTypeAdapterFactory(jsonObject -> {
                if ( !jsonObject.has("fieldName") ) {
                    return defaultFilterType;
                }
                switch ( jsonObject.get("fieldName").getAsString() ) {
                case "barInteger":
                    return integerFilterType;
                case "fooDate":
                    return dateFilterType;
                default:
                    return defaultFilterType;
                }
            }))
            .create();
    

    请注意,TypeTokens 和 Types 可以被认为是不可变的和常量,因此可以将它们放在单独的类中:

    final class Types {
    
        private Types() {
        }
    
        static final Type defaultFilterType = new TypeToken<Filter<Object>>() {
        }.getType();
    
        static final Type integerFilterType = new TypeToken<Filter<Integer>>() {
        }.getType();
    
        static final Type dateFilterType = new TypeToken<Filter<Date>>() {
        }.getType();
    
    }
    

    现在,对于这两个策略,下面的代码

    final FilterInfo filterInfo = gson.fromJson(JSON, FilterInfo.class);
    System.out.println(filterInfo.orderBy);
    for ( final Filter filter : filterInfo.filters ) {
        System.out.println(filter.fieldName + filter.operand + filter.value + " of " + filter.value.getClass());
    }
    

    将输出:

    美食日期
    fooDate=Sat Dec 20 00:00:00 EET 1997 of class java.util.Date
    barInteger>=10 of class java.lang.Integer

    【讨论】:

    • 但是...您能否帮我将 lambda 函数重构为 Java 6。我是这样开始的...但无法完成。 Function&lt;? super Type, ? extends TypeAdapter&lt;T&gt;&gt; typeTypeAdapterFunction = new Function&lt;Type, TypeAdapter&lt;T&gt;&gt;() { @Override public TypeAdapter&lt;T&gt; apply(Type input) { return null; // } };非常感谢!
    • 要将 lamdas 反向移植到 java 6,只需编写一个匿名内部类:new FilterTypeAdapterFactory() {...}。然后,您将不再拥有 getFilterTypeAdapterFactory 方法。您可以改为创建 FilterTypeAdapterFactory 局部变量。
    • @AzamatAlmukhametov 匿名函数的主体是@SuppressWarnings("unchecked") final TypeToken&lt;T&gt; delegateTypeToken = (TypeToken&lt;T&gt;) TypeToken.get(type); return gson.getDelegateAdapter(FilterTypeAdapterFactory.this, delegateTypeToken);(注意FilterTypeAdapterFactory.this——你可以“捕获”封闭类this,而lambdas允许省略这个语法;如果使用@987654352 @ 在匿名类中,然后this 像往常一样指向匿名类)。
    • 亲爱的,@LyubomyrShaydariv,非常感谢。你知道,实际上在我的情况下,我还没有准备好完全理解这个 TypeAdapterFactory。所以,当时我决定实现自定义代码来转换已被标准 Gson 适配器反序列化的对象......将来我希望更深入地学习 Gson 框架......谢谢!
    • @AzamatAlmukhametov 很高兴能帮到你,不客气!
    猜你喜欢
    • 1970-01-01
    • 2021-12-17
    • 2021-06-22
    • 1970-01-01
    • 2023-02-24
    • 2013-01-13
    • 2010-11-13
    • 2015-10-18
    • 2021-08-16
    相关资源
    最近更新 更多