java - 我的 CustomDeserializer 类在同一类的第二个字段上第二次不起作用
问题描述
我正在使用 Jackson 依赖项,我认为问题在于 jsonParser 被调用了三次以上。但我不确定为什么会这样......我有这个案例:
@Entity
public class Car implements Serializable {
@JsonDeserialize(using = CustomDeserialize.class)
private Window windowOne:
@JsonDeserialize(using = CustomDeserialize.class)
private Window windowSecond:
....//Getters/Setters
}
CustomDeserializer 类
public class CustomDeserializer extends StdDeserializer<Window> {
..... // constructors
@Override
public Window deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
String field = jsonParser.nextFieldName();
String nextField = jsonParser.nextFieldName();
return new Window("value1", "valu2");
}
}
调用 objectMapper 的管理器类
public class Manager {
private ObjectMapper mapper = new ObjectMapper();
public void serializeCar(ObjectNode node) {
// node comes loaded with valid values two windows of a Car.
// All is OK until here, so this treeToValue enters to CustomDeserializer once only.
// treeToValue just read the first window ? because of second window is null and the first window enters on mode debug.
Car car = mapper.treeToValue(node, Car.class);
}
}
当我调试时,我不知道为什么 treeToValue(objectNode, class) 只调用一次 CustomSerializer 类而第二次不调用它。请问这里有什么问题吗?或者为什么 mapper.treeToValue 使用 CustomDeserializer 忽略第二个字段?先谢谢各位专家了。
更新
我添加了一个存储库作为示例:
解决方案
您的解串器工作不正常。
当您到达windowOne时,您正在阅读接下来两个字段的名称 -"windowSecond"
并且null
(因为我们没有标记) - 而不是JsonNode
您已阅读的值。当序列化程序返回时,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"
}
}
解析器对此进行标记以允许我们使用它。让我们将 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"
它知道它应该被Window
反序列化,CustomDeserialize
因此它创建 aCustomDeserialize
并调用它的deserialize
方法。
然后反序列化器调用JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
期望下一个令牌是一个START_OBJECT
令牌并解析所有内容直到匹配的END_OBJECT
令牌,并将其作为JsonNode
.
这将返回JsonNode
代表此 JSON 的 a:
{
"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();
记录为:
获取下一个令牌的方法(就像调用 nextToken 一样)并验证它是否是 JsonToken.FIELD_NAME;如果是,则返回与 getCurrentName() 相同,否则返回 null
即它消耗FIELD_NAME: "windowSecond"
和返回"windowSecond"
。然后您再次调用它,但由于下一个标记是START_OBJECT
this 返回 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
您的反序列化器Window
通过传递field + nextField
( ="windowSecondnull"
) 和jsonNode.getNodeType().toString
( ="OBJECT"
) 将其转换为 a ,然后返回,将解析器的控制权传递回 Jackson,Jackson 首先设置Car.value1
您的反序列化器返回的窗口,然后继续解析。
这就是它变得有点奇怪的地方。在您的反序列化器返回后,Jackson 期待一个FIELD_NAME
令牌,并且由于您消耗了START_OBJECT
令牌,因此它得到了一个。但是,它得到FIELD_NAME: "value1"
并且因为Car
没有任何命名的属性value1
,并且您已将 Jackson 配置为忽略未知属性,所以它会跳过此字段及其值并继续移动到FIELD_NAME: "value2"
导致相同行为的位置。
现在剩余的令牌看起来像这样:
END_OBJECT
END_OBJECT
下一个标记END_OBJECT
表明您Car
已正确反序列化,因此杰克逊返回。
这里要注意的是解析器仍然有一个剩余的令牌,最后一个END_OBJECT
但由于杰克逊默认忽略剩余的令牌,这不会导致任何错误。
如果您想看到它失败,请删除该行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"));
}
推荐阅读
- java - 自动装配层次结构类的最佳实践是什么?
- java - 为什么只能在 IntelliJ 的目标/类中看到本机库?
- python - 如何在 Selenium 中循环“刷新页面”
- python - Django - 在模板中访问链的 ForigenKey 对象字段
- django - 我可以在 Django 1.11 和 Django 2.x 之间共享数据库吗?
- javascript - 如何扩展 div 宽度以在反应应用程序中显示更多内容
- d3.js - D3 显示来自 API 调用的文本
- excel - 使用 VBA 将范围写入公式
- javascript - 在 .append jquery 内部循环
- python - 我的主要过滤代码有什么问题?它不会返回正确的列表