使GSON在需要数组的地方接受单个对象

z6psavjg  于 2022-11-06  发布在  其他
关注(0)|答案(5)|浏览(171)

我有一堆模型类,它们有List<X>类型的字段,其中X是许多类型中的一种(例如StringInteger,但也有我自己的一些类型)。
我的问题是,我正在处理的服务器(这超出了我的控制范围)不知何故删除了单例数组,并用包含的对象替换了它们。
例如,不返回:

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

它会传回:

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

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

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

目前,这会导致GSON抛出异常,因为它在期望BEGIN_ARRAY的地方找到了BEGIN_STRINGBEGIN_OBJECT
对于数组或字符串列表,使用TypeAdapter<List<String>>可以很容易地解决这个问题。但问题是,我有许多不同元素类型的List,我不想为每种情况编写单独的TypeAdapter。我也不能编写通用的TypeAdapter<List<?>>。因为在某些情况下您需要知道类型。那么有没有其他方法可以将GSON配置得足够智能,只是“假装”[]在它希望找到它们的地方,尽管它们并不在那里?

mtb9vblg

mtb9vblg1#

但问题是我有很多不同元素类型的List,我不想为每种情况编写一个单独的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 + '\'' + '}';
    }

}

试验数据:

单个. json

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

列表. 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=[某个对象{某个='对象'}]}
模型{foo=[bar],bleh=[某个对象{某个='对象'}]}

xu3bshqb

xu3bshqb2#

您可以简单地编写自己的JsonDeserializer,在其中检查您的blehfoo是JsonObjects还是JsonArrays。
要检查JsonElement是数组还是对象,请执行以下操作:

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

oxalkeyp3#

当使用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()
}

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

8xiog9wr

8xiog9wr4#

我在使用一个供应商的xml / json时也遇到了同样的问题--他们肯定不会为我修改他们的代码:)在修改之前,我使用了网上的几个资源来适应我自己的This SO answer was very helpful版本。我花了一些时间查看gson代码,发现了很多我想要访问的私有变量。所以,本质上,我的自定义集合适配器所做的就是查看下一个元素是否是对象。如果不是,我们就把读取委托给上一个适配器(我们已经覆盖了)。
如果下一个元素是一个对象,我们使用gson来处理它。然后我们将它转换成一个对象的数组。使用gson将它写入一个字符串,然后将该字符串作为一个JsonReader传递给底层适配器。然后,这可以创建底层列表的一个示例,并添加我们拥有的一个元素。
下面是适配器类型工厂:

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

到目前为止,它似乎在处理单个和多个对象方面都工作得很好--尽管如果它需要一些调整,我也不会感到惊讶。

gstyhher

gstyhher5#

一种解决方案是编写一个定制的TypeAdapterFactory,它创建一个适配器来查看JSON数据。如果遇到JSON数组以外的数据(或JSON空值),它会在反序列化之前将其 Package 在JSON数组中:

// Only intended for usage with @JsonAdapter on fields
class SingleValueOrListAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        // Note: Cannot use getDelegateAdapter due to https://github.com/google/gson/issues/1028
        TypeAdapter<T> listAdapterDelegate = gson.getAdapter(type);
        TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                listAdapterDelegate.write(out, value);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                JsonToken peeked = in.peek();
                if (peeked == JsonToken.NULL || peeked == JsonToken.BEGIN_ARRAY) {
                    return listAdapterDelegate.read(in);
                } else {
                    // Wrap JSON element in a new JSON array before deserializing it
                    JsonElement jsonElement = jsonElementAdapter.read(in);
                    JsonArray jsonArray = new JsonArray();
                    jsonArray.add(jsonElement);

                    return listAdapterDelegate.fromJsonTree(jsonArray);
                }
            }
        };
    }
}

上述实现仅设计用于字段上的@JsonAdapter,例如:

@JsonAdapter(SingleValueOrListAdapterFactory.class)
private List<MyClass> myField;

currently accepted answer相比,这提供了以下优点,因为它只是将实际的反序列化委托给listAdapterDelegate

  • 支持自定义List(或Collection)子类,因为它们的创建已委托给Gson
  • Gson的默认类型解析逻辑用于确定元素类型并将其反序列化

但它也有以下缺点:

  • 性能降低,因为如果数据尚未在JSON数组中,则会在执行实际的反序列化之前先将其反序列化为JsonElement

相关问题