我的 CustomDeserializer 类在同一类的第二个字段上第二次不起作用
Posted
技术标签:
【中文标题】我的 CustomDeserializer 类在同一类的第二个字段上第二次不起作用【英文标题】:My CustomDeserializer class doesn't work second time that is used on second field of a same class 【发布时间】:2019-05-08 18:09:46 【问题描述】:我正在使用 Jackson 依赖项,我认为问题在于 jsonParser 被调用了 3 次以上。但我不确定为什么会这样...... 我有这种情况:
@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的Manager类
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 忽略第二个字段? 提前感谢各位专家。
更新
我添加了一个存储库作为示例:
https://github.com/NextSoftTis/demo-deserializer
【问题讨论】:
你能在 github 上分享这个 bug 的 poc 吗? 我想,没必要。因为你有类作为例子,但如果你真的需要它,我会创建它。 我对我的一个应用程序使用相同的配置,但我无法发现你的 wath 有问题,如果你可以创建一个 github 存储库,其中包含需要重现的 juste wath,我会尝试为你修好它 完成github.com/NextSoftTis/demo-deserializer 顺便说一句,在示例存储库上做得很好。有一个可运行的示例可以让回答问题很多更容易! 【参考方案1】:您的反序列化器工作不正常。
当您到达 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_OBJECT
but 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"));
【讨论】:
看起来不错,我有一个问题,当你在反序列化方法中多次调用 jsonParser 时,这是我认为的主要问题,你知道为什么我不能多次调用 jsonParser 来做一些逻辑..? 是的,我也有@nextsoft 说的同样的问题,多次调用jsonParser 不正确吗?或者为什么有些事情会通过...... 我无法在评论中添加解释,因此更新了答案以上是关于我的 CustomDeserializer 类在同一类的第二个字段上第二次不起作用的主要内容,如果未能解决你的问题,请参考以下文章