使 GSON 接受它需要数组的单个对象

2022-09-03 18:14:06

我有一堆模型类,它们具有类型的字段,其中是许多事物之一(例如,,,但也有我自己的一些类型)。我正在使用GSON来解析这些模型的JSON表示形式。List<X>XStringInteger

我的问题是,我正在处理的服务器(这超出了我的控制范围)以某种方式删除了单例数组,并用包含的对象替换它们。

例如,不要返回:

{
  "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_OBJECTBEGIN_ARRAY

对于数组或字符串列表,使用 .但问题是我有许多不同的元素类型,我不想为每个情况单独编写一个。我也无法使用泛型,因为在某些时候你需要知道类型。那么,有没有另一种方法可以将GSON配置为足够智能,可以将单个对象或值转换为数组/列表?或者换句话说,只是“假装”和在那里,它期望找到它们,尽管它们不在那里?TypeAdapter<List<String>>ListTypeAdapterTypeAdapter<List<?>>[]


答案 1

但问题是我有许多不同元素类型的Lists,我不想为每种情况下编写单独的TypeAdapter。我也无法使用泛型 TypeAdapter>,因为在某些时候你需要知道类型。

这就是类型适配器工厂的设计目的:您可以在实例配置中控制每种类型。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);
        }
    }
}

输出:

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


答案 2

您只需编写自己的 JsonDeserializer,在其中检查您的 JsonObjects 或 JsonArrays。blehfoo

要检查 JsonElement 是数组还是对象,请执行以下操作:

JsonElement element = ...;
if (element.isJsonObject()) {
    //element is a JsonObject
} else if (element.isJsonArray()) {
    //element is a JsonArray
}