当前位置: 首页 > 知识库问答 >
问题:

去序列化动态字段

沈伟
2023-03-14


如果发生错误,数据栏将是这样的字符串:

{
  "code": 2001,
  "data": "Error!"
}

如果成功,数据字段将是一个对象:

{
  "code": 2000,
  "data": {
    "id": 1,
    "name": "example"
  }
}

我使用以下Kotlin代码对其进行反序列化:

return Gson().fromJson(data, SimpleModel::class.java)

模型定义如下:

import com.google.gson.annotations.SerializedName

class SimpleModel {
    @SerializedName("code")
    val code = 0

    @SerializedName("data")
    val data: SimpleData = SimpleData()
}

class SimpleData {
    @SerializedName("id")
    val id = ""

    @SerializedName("name")
    val name = ""
}

当没有错误发生时,上面的代码工作正常。但是当错误发生时,会引发异常:

java.lang.IllegalStateException: Excepted BEGIN_OBJECT but was STRING at line 1 column x $path.data

有没有办法将数据字段反序列化为对象或任何对象,并通过代码手动确定其类型?

共有2个答案

卢嘉誉
2023-03-14

像前面的答案一样,我不熟悉静态编程语言,下面的解决方案Java,但正如我所知,使用IntelliJ内置工具很容易将Java转换为静态编程语言。

成功/错误对象对是一个经典问题,您可以创建自己的解决方法,但是我们考虑以下类分别表示成功和错误对象(java 17,然后启用交换机上的模式匹配):

abstract sealed class SimpleModel<T>
        permits SimpleModelSuccess, SimpleModelError {

    @SerializedName("code")
    final int code;

    SimpleModel(final int code) {
        this.code = code;
    }

}

final class SimpleModelSuccess<T>
        extends SimpleModel<T> {

    @SerializedName("data")
    final T data;

    private SimpleModelSuccess(final int code, final T data) {
        super(code);
        this.data = data;
    }

}

final class SimpleModelError<T>
        extends SimpleModel<T> {

    @SerializedName("data") // the annotation is helping here!
    final String message;

    private SimpleModelError(final int code, final String message) {
        super(code);
        this.message = message;
    }

}

上面的代码可以自我解释。现在的核心部分需要比我之前想象的更多的工作,我给你们提供了我的第一条评论,这似乎是不完整的。

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
final class SimpleModelTypeAdapterFactory
        implements TypeAdapterFactory {

    @Getter
    private static final TypeAdapterFactory instance = new SimpleModelTypeAdapterFactory();

    @Override
    @Nullable
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( !SimpleModel.class.isAssignableFrom(typeToken.getRawType()) ) {
            return null;
        }
        // let's figure out what the model is parameterized with
        final Type type = typeToken.getType();
        final Type typeParameter;
        if ( type instanceof ParameterizedType parameterizedType ) {
            typeParameter = parameterizedType.getActualTypeArguments()[0];
        } else {
            throw new UnsupportedOperationException("Cannot infer type parameter from " + type);
        }
        // then borrow their respective type adapters for both success and error cases
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> successDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelSuccess.class, typeParameter));
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> errorDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelError.class, typeParameter));
        return new TypeAdapter<>() {
            @Override
            public void write(final JsonWriter out, final T value) {
                throw new UnsupportedOperationException();
            }

            @Override
            public T read(final JsonReader in) {
                // buffer the JSON tree first
                // note that this solution may be very inefficient under some circumstances
                final JsonObject buffer = Streams.parse(in).getAsJsonObject();
                final JsonElement dataElement = buffer.get("data");
                // is it's data is {...}, the consider it is success (by the way, what is code about?)
                if ( dataElement.isJsonObject() ) {
                    return successDelegate.fromJsonTree(buffer);
                }
                // if it's a primitive, consider it's an error
                if ( dataElement.isJsonPrimitive() ) {
                    return errorDelegate.fromJsonTree(buffer);
                }
                // well we've done our best...
                throw new JsonParseException(String.format("Cannot deduce the model for %s", buffer.getClass()));
            }
        };
    }

}
java prettyprint-override">public final class SimpleModelTypeAdapterFactoryTest {

    private static final class SomeJsonProvider
            implements ArgumentsProvider {

        @Override
        public Stream<? extends Arguments> provideArguments(final ExtensionContext context) {
            return Stream.of(
                    Arguments.of(
                            """
                            {
                                "code": 2000,
                                "data": {
                                    "id": 1,
                                    "name": "example"
                                }
                            }
                            """
                    ),
                    Arguments.of(
                            """
                            {
                                "code": 2001,
                                "data": "Error!"
                            }
                            """
                    )
            );
        }

    }

    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    @ToString
    private static final class SimpleData {

        private final String id;
        private final String name;

    }

    private static final Type simpleModelOfSimpleDataType = TypeToken.getParameterized(SimpleModel.class, SimpleData.class)
            .getType();

    @ParameterizedTest
    @ArgumentsSource(SomeJsonProvider.class)
    public void test(final String json) {
        final Gson gson = new GsonBuilder()
                .disableHtmlEscaping()
                .disableInnerClassSerialization()
                .registerTypeAdapterFactory(SimpleModelTypeAdapterFactory.getInstance())
                .create();
        final SimpleModel<SimpleData> model = gson.fromJson(json, simpleModelOfSimpleDataType);
        switch ( model ) {
        case SimpleModelSuccess<SimpleData> success -> System.out.println(success.data);
        case SimpleModelError<SimpleData> error -> System.out.println(error.message);
        }
    }

}

以下是它打印到标准输出的内容:

SimpleModelTypeAdapterFactoryTest. SimpleData(id=1, name=示例)
错误!

嗯,是的,这比我第一次评论中提到的要“有点”棘手。

齐文林
2023-03-14

您需要编写一个自定义的反序列化程序,它根据节点的运行时类型决定将数据反序列化到哪个类型,并将该反序列化程序注册到Gson实例中。不幸的是,我不熟悉kotlin语法,所以我只能给出伪代码。

  1. SimpleModel中的字段data应该是Object,或者将类设置为泛型-SimpleModel
JsonElement root = parseResponse();
root.getAsJsonObject().get("data").getAsString();

你可以用我的答案作为灵感。虽然它是关于对象映射器的,但它做同样的事情——根据节点类型决定对象类型,并遵循与我上面描述的大致相同的算法。

此外,本指南还提供了有关如何编写或注册自己的反序列化程序的信息。

 类似资料:
  • 是否有方法序列化类的瞬态字段?文档中提到默认情况下不支持它,但是否有办法打开它? 非常感谢

  • 问题内容: 我有以下需要反序列化的Json字符串。 第一个字段“ 123456789”是一个ID号,因此基本上该值可以根据要查询的数据而有所不同。 我在Visual Studio中使用C#。显然,因为第一个字段的值可以更改,所以我无法使用预定义的类将JSON反序列化为该字段,因为该字段将用作类名,但该字段的值与该类名不匹配。 有没有一种方法可以将其反序列化为某种动态类,但仍可以像访问预定义类一样访

  • 问题内容: 在Java中进行序列化后,是否可以将字段设置为任何非默认值?我的用例是一个缓存变量- 这就是为什么。我也有一个习惯,即不要更改字段(即,地图的内容已更改,但对象本身保持不变)。但是,这些属性似乎是矛盾的- 尽管编译器允许这样的组合,但除反序列化之后,我无法将字段设置为任何值。 我尝试了以下操作,但没有成功: 简单的字段初始化(如示例所示):这是我通常所做的,但是在未序列化之后似乎没有发

  • 考虑以下代码: 现在扩展了一个实现接口的类。类和是带有一堆getter和setter的POJOS。FindBugs抱怨和字段说: 这个Serializable类定义了一个非基元实例字段,它既不是瞬态的、Serializable的,也不java.lang.Object的,并且似乎没有实现Externalizable接口或readObject()和WriteObject()方法。 好吧,所以一切都很好

  • 从我的Java后端,我正在使用另一个我不管理的后端,它的API定义不可用。我正在创建它的服务的OpenAPI定义,并使用Swagger Codigen来生成客户端。 有一个endpoint返回一个复杂对象: 在该对象中,和始终存在,但在同一级别上有数百个动态项。在上面的示例中,关键点是可以预测的,但实际上是字母和数字的序列,类似于“245df921”。动态项始终是具有相同项数和相同预期位置的数组。

  • 我有一个没有扩展可串行化或可外部化接口的类,但在netbeans中使用FindBugs时仍然会遇到错误。有人能告诉我如何解决这个问题吗? 这是我的课 FindBug中的Bug:类分析器定义了一个非瞬时的非序列化实例字段objAnalyzerVar,对于objAnalyzerDataTypeInfo也是如此;