让GSON接受期望数组的单个对象

时间:2017-04-14 13:13:10

标签: java json gson

我有一堆模型类,其中包含List<X>类型的字段,其中X是众多事物之一(例如StringInteger,但也有我自己的一些类型)。我使用GSON来解析这些模型的JSON表示。

我的问题是我处理的服务器(我无法控制)以某种方式删除单例数组并用包含的对象替换它们。

例如,而不是返回:

{
  "foo": [ "bar"],
  "bleh": [ { "some": "object" } ]
}

它返回:

{
  "foo": "bar",
  "bleh": { "some": "object" }
}

现在假设Java模型类看起来像这样:

public class Model {
   private List<String> foo;
   private List<SomeObject> bleh;
}

目前这会导致GSON抛出异常,因为它会发现BEGIN_STRINGBEGIN_OBJECT所需的BEGIN_ARRAY

对于数组或字符串列表,可以使用TypeAdapter<List<String>>轻松解决。但问题是我List有许多不同的元素类型,我不想为每个案例写一个单独的TypeAdapter。我也没有能够使用通用TypeAdapter<List<?>>,因为在某些时候你需要知道类型。 那么还有另一种方法可以将GSON配置为足够聪明以将单个对象或值转换为数组/列表吗?或者换句话说,只是&#34;假装&#34; []在哪里可以找到它们,尽管它们不在那里?

4 个答案:

答案 0 :(得分:18)

  

但问题是我有许多不同元素类型的列表,我不想为每种情况编写单独的TypeAdapter。我也没有能够使用通用的TypeAdapter&gt;,因为在某些时候你需要知道类型。

这是适配器工厂的类型:您可以控制Gson实例配置中的每种类型。

final class AlwaysListTypeAdapterFactory<E>
        implements TypeAdapterFactory {

    // Gson can instantiate it itself
    private AlwaysListTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // If it's not a List -- just delegate the job to Gson and let it pick the best type adapter itself
        if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
            return null;
        }
        // Resolving the list parameter type
        final Type elementType = resolveTypeArgument(typeToken.getType());
        @SuppressWarnings("unchecked")
        final TypeAdapter<E> elementTypeAdapter = (TypeAdapter<E>) gson.getAdapter(TypeToken.get(elementType));
        // Note that the always-list type adapter is made null-safe, so we don't have to check nulls ourselves
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> alwaysListTypeAdapter = (TypeAdapter<T>) new AlwaysListTypeAdapter<>(elementTypeAdapter).nullSafe();
        return alwaysListTypeAdapter;
    }

    private static Type resolveTypeArgument(final Type type) {
        // The given type is not parameterized?
        if ( !(type instanceof ParameterizedType) ) {
            // No, raw
            return Object.class;
        }
        final ParameterizedType parameterizedType = (ParameterizedType) type;
        return parameterizedType.getActualTypeArguments()[0];
    }

    private static final class AlwaysListTypeAdapter<E>
            extends TypeAdapter<List<E>> {

        private final TypeAdapter<E> elementTypeAdapter;

        private AlwaysListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
            this.elementTypeAdapter = elementTypeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final List<E> list) {
            throw new UnsupportedOperationException();
        }

        @Override
        public List<E> read(final JsonReader in)
                throws IOException {
            // This is where we detect the list "type"
            final List<E> list = new ArrayList<>();
            final JsonToken token = in.peek();
            switch ( token ) {
            case BEGIN_ARRAY:
                // If it's a regular list, just consume [, <all elements>, and ]
                in.beginArray();
                while ( in.hasNext() ) {
                    list.add(elementTypeAdapter.read(in));
                }
                in.endArray();
                break;
            case BEGIN_OBJECT:
            case STRING:
            case NUMBER:
            case BOOLEAN:
                // An object or a primitive? Just add the current value to the result list
                list.add(elementTypeAdapter.read(in));
                break;
            case NULL:
                throw new AssertionError("Must never happen: check if the type adapter configured with .nullSafe()");
            case NAME:
            case END_ARRAY:
            case END_OBJECT:
            case END_DOCUMENT:
                throw new MalformedJsonException("Unexpected token: " + token);
            default:
                throw new AssertionError("Must never happen: " + token);
            }
            return list;
        }

    }

}

现在你只需要告诉Gson 哪些字段格式不正确。 当然,您可以将整个Gson实例配置为接受此类列表,但使用@JsonAdapter注释更准确:

final class Model {

    @JsonAdapter(AlwaysListTypeAdapterFactory.class)
    final List<String> foo = null;

    @JsonAdapter(AlwaysListTypeAdapterFactory.class)
    final List<SomeObject> bleh = null;

    @Override
    public String toString() {
        return "Model{" + "foo=" + foo + ", bleh=" + bleh + '}';
    }

}

final class SomeObject {

    final String some = null;

    @Override
    public String toString() {
        return "SomeObject{" + "some='" + some + '\'' + '}';
    }

}

测试数据:

single.json

{
    "foo": "bar",
    "bleh": {"some": "object"}
}

list.json

{
    "foo": ["bar"],
    "bleh": [{"some": "object"}]
}

示例:

private static final Gson gson = new Gson();

public static void main(final String... args)
        throws IOException {
    for ( final String resource : ImmutableList.of("single.json", "list.json") ) {
        try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43412261.class, resource) ) {
            final Model model = gson.fromJson(jsonReader, Model.class);
            System.out.println(model);
        }
    }
}

输出:

  

模型{foo = [bar],bleh = [SomeObject {some ='object'}]}
  模型{foo = [bar],bleh = [SomeObject {some ='object'}]}

答案 1 :(得分:2)

您只需编写自己的JsonDeserializer,即可检查template header name package test; template "test template" rule "test rule" when $fact : Fact(code != ) // expression is wrong intentionally then $fact.setOk(false); end end template bleh是JsonObjects还是JsonArrays。

检查JsonElement是数组还是对象:

foo

答案 2 :(得分:0)

使用GSON库时,您只需检查以下标记是对象还是数组即可。当然,这需要您在解析XML时更加细化,但是它使您可以完全控制要从中获得什么。有时我们不受XML的控制,它可能派上用场。

这是使用JsonReader类分析文件来检查下一个标记是对象还是数组的示例:

if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
    jsonReader.beginArray()
} else if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
    jsonReader.beginObject()
}

在数组/对象的末尾,您可以执行相同的操作,但对于结束标记:

if (jsonReader.peek() == JsonToken.END_ARRAY) {
    jsonReader.endArray()
} else if (jsonReader.peek() == JsonToken.END_OBJECT) {
    jsonReader.endObject()
}

这样,您可以使用相同的代码(添加额外的检查以验证您是在数组还是对象上)来解析对象数组或单个对象。

答案 3 :(得分:0)

我在使用供应商提供的xml / json时也遇到了同样的问题-他们当然不会为我更改代码:)在更改适应他们自己的版本{{之前, 3}}。我花了一些时间查看gson代码,并找到了许多我想访问的私有变量。因此,从本质上讲,我的自定义集合适配器所做的就是偷看下一个元素是否是一个对象。如果没有,我们只将读取委托给先前的适配器(已被覆盖)。

如果下一个元素是一个对象,我们将使用gson进行处理。然后,我们将其转换为一个对象的数组。使用gson将其写入字符串,然后将该字符串作为JsonReader传递给基础适配器。然后,可以创建基础列表的实例并添加我们拥有的一个元素。

这是AdapterTypeFactory:

    public enum ListSingleObjectAdapterFactory implements TypeAdapterFactory {
    INSTANCE; // Josh Bloch's Enum singleton pattern

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {

        Class<? super T> rawType = typeToken.getRawType();
        if (!Collection.class.isAssignableFrom(rawType)) {
            return null;
        }

        TypeAdapter collectionAdapter = gson.getDelegateAdapter(this, typeToken);
        Class genericClass = (Class) ((ParameterizedType) typeToken.getType())
                .getActualTypeArguments()[0];
                return new SingleObjectOrCollectionAdapter(
                        gson, collectionAdapter, genericClass);
    }
}

然后我拥有的类型适配器是:

public class SingleObjectOrCollectionAdapter<T> extends TypeAdapter<Collection<T>> {
    private Class<T> adapterclass;
    private Gson gson;
    private TypeAdapter arrayTypeAdapter;


    public SingleObjectOrCollectionAdapter(Gson gson, TypeAdapter<T> collectionTypeAdapter, Class<T> componentType) {
        arrayTypeAdapter = collectionTypeAdapter;
        this.gson = gson;
        adapterclass = componentType;
    }


    @Override
    public Collection<T> read(JsonReader reader) throws IOException {
    Collection<T> collection;
        JsonReader myReader = reader;
        if (reader.peek() == JsonToken.BEGIN_OBJECT) {
            T inning = gson.fromJson(reader, adapterclass);
            String s = gson.toJson(new Object[]{inning});
            myReader = new JsonReader(new StringReader(s));

        }
        collection = (Collection)arrayTypeAdapter.read( myReader );

        return collection;
    }

    @Override
    public void write(JsonWriter writer, Collection<T> value) throws IOException {
        arrayTypeAdapter.write(writer, value);
    }
}

最后,我们需要注册适配器工厂:

GsonBuilder gb = new GsonBuilder().registerTypeAdapterFactory(ListSingleObjectAdapterFactory.INSTANCE);

到目前为止,似乎可以很好地处理单个和多个对象-尽管如果需要进行一些调整,我不会感到惊讶。