您的反序列化器工作不正常。
当您到达 windowOne 时,您正在读取接下来两个字段的名称 - "windowSecond" 和 null(因为我们没有令牌) - 而不是 @ 的值987654325@您已阅读。当序列化程序返回时,Jackson 会看到没有更多的令牌并跳过 windowSecond 的反序列化,因为没有更多数据可以使用。
@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
String field = jsonParser.nextFieldName();
String nextField = jsonParser.nextFieldName();
return new Window(field + nextField, jsonNode.getNodeType().toString());
}
您可以通过查看示例程序的输出看到这一点:
{
"windowOne": {
"value1": "windowSecondnull",
"value2": "OBJECT"
},
"windowSecond": null
}
(顺便说一下,您的示例代码库不包含您在此处发布的相同代码)。
线条:
String field = jsonParser.nextFieldName();
String nextField = jsonParser.nextFieldName();
是问题所在,你应该改用你读过的JsonNode,它会按预期工作:
@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
String value1 = jsonNode.hasNonNull("value1") ? jsonNode.get("value1").asText() : null;
String value2 = jsonNode.hasNonNull("value2") ? jsonNode.get("value2").asText() : null;
return new Window(value1, value2);
}
回复:
{
"windowOne": {
"value1": "Testing 1",
"value2": "Testing 2"
},
"windowSecond": {
"value1": "Testing 1 1",
"value2": "Testing 1 2"
}
}
深入讲解
为了详细说明原始代码中究竟发生了什么,让我们简单看看 JSON 解析器中发生了什么:
我们正在解析的构造 JsonNode 表示以下 JSON:
{
"windowOne": {
"value1": "Testing 1",
"value2": "Testing 2"
},
"windowSecond": {
"value1": "Testing 1 1",
"value2": "Testing 1 2"
}
}
解析器tokenizes 这允许我们使用它。让我们将 this 的标记化状态表示为这个标记列表:
START_OBJECT
FIELD_NAME: "windowOne"
START_OBJECT
FIELD_NAME: "value1"
VALUE: "Testing 1"
FIELD_NAME: "value2"
VALUE: "Testing 2"
END_OBJECT
FIELD_NAME: "windowSecond"
START_OBJECT
FIELD_NAME: "value1"
VALUE: "Testing 1 1"
FIELD_NAME: "value2"
VALUE: "Testing 1 2"
END_OBJECT
杰克逊通过这些代币,试图用它建造一辆汽车。它找到START_OBJECT,然后找到FIELD_NAME: "windowOne",它知道应该是CustomDeserialize反序列化的Window,因此它创建了一个CustomDeserialize并调用它的deserialize方法。
然后反序列化器调用JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);,它期望下一个令牌是START_OBJECT 令牌并解析所有内容直到匹配的END_OBJECT 令牌,将其作为JsonNode 返回。
这将返回代表此 JSON 的 JsonNode:
{
"value1": "window 2 value 1",
"value2": "window 2 value 2"
}
解析器中剩余的标记将是:
FIELD_NAME: "windowSecond"
START_OBJECT
FIELD_NAME: "value1"
VALUE: "Testing 1 1"
FIELD_NAME: "value2"
VALUE: "Testing 1 2"
END_OBJECT
END_OBJECT
然后您调用String field = jsonParser.nextFieldName();,记录为:
获取下一个token的方法(如同调用nextToken)并验证是否为JsonToken.FIELD_NAME;如果是,则返回与 getCurrentName() 相同,否则返回 null
即它消耗FIELD_NAME: "windowSecond" 并返回"windowSecond"。然后您再次调用它,但由于下一个令牌是 START_OBJECT,因此返回 null。
我们现在有
field = "windowSecond"
nextField = null
jsonNode.getNodeType().toString() = "OBJECT"
以及剩余的令牌:
FIELD_NAME: "value1"
VALUE: "Testing 1 1"
FIELD_NAME: "value2"
VALUE: "Testing 1 2"
END_OBJECT
END_OBJECT
您的反序列化器通过传递 field + nextField (="windowSecondnull") 和 jsonNode.getNodeType().toString (="OBJECT") 将其转换为 Window,然后返回,将解析器的控制权传递回 Jackson,Jackson 首先将 Car.value1 设置为您的反序列化器返回的窗口,然后继续解析。
这里有点奇怪。在您的反序列化器返回后,Jackson 期待一个 FIELD_NAME 令牌,并且由于您使用了 START_OBJECT 令牌,它得到一个。但是,它得到 FIELD_NAME: "value1" 并且由于 Car 没有任何名为 value1 的属性 并且 您已将 Jackson 配置为忽略未知属性,它会跳过该字段及其值并转到 @ 987654365@ 导致相同的行为。
现在剩余的令牌如下所示:
END_OBJECT
END_OBJECT
下一个标记是 END_OBJECT,这表明您的 Car 已正确反序列化,因此 Jackson 返回。
这里要注意的是解析器还有一个剩余的标记,最后一个END_OBJECTbut since Jackson ignores remaining tokens by default不会导致任何错误。
如果您想看到它失败,请删除 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 行:
无法识别的字段“value1”(com.example.demodeserializer.Car 类),未标记为可忽略(2 个已知属性:“windowSecond”、“windowOne”])
使用令牌的自定义反序列化器
要编写一个多次调用解析器的自定义反序列化器,我们需要删除 JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser); 行并自己处理令牌。
我们可以这样做:
@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext dc) throws IOException, JsonProcessingException {
// Assert that the current token is a START_OBJECT token
if (jsonParser.currentToken() != JsonToken.START_OBJECT) {
throw dc.wrongTokenException(jsonParser, Window.class, JsonToken.START_OBJECT, "Expected start of Window");
}
// Read the next two attributes with value and put them in a map
// Putting the attributes in a map means we ignore the order of the attributes
final Map<String, String> attributes = new HashMap<>();
attributes.put(jsonParser.nextFieldName(), jsonParser.nextTextValue());
attributes.put(jsonParser.nextFieldName(), jsonParser.nextTextValue());
// Assert that the next token is an END_OBJECT token
if (jsonParser.nextToken() != JsonToken.END_OBJECT) {
throw dc.wrongTokenException(jsonParser, Window.class, JsonToken.END_OBJECT, "Expected end of Window");
}
// Create a new window and return it
return new Window(attributes.get("value1"), attributes.get("value2"));
}