我正在使用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忽略第二个字段? 预先感谢专家。
已更新
我添加了一个存储库作为示例:
答案 0 :(得分:3)
您的反序列化器无法正常工作。
当您到达 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"
}
}
解析器tokenizes,使我们可以使用它。让我们将其标记状态表示为以下标记列表:
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
杰克逊(Jackson)经历了这些标记,试图用它建造一辆汽车。它先找到START_OBJECT
,然后找到FIELD_NAME: "windowOne"
,然后知道该Window
由CustomDeserialize
反序列化,所以它创建了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();
,其记录为:
获取下一个令牌(就像调用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
您的反序列化器通过传递Window
(field + nextField
)和="windowSecondnull"
(jsonNode.getNodeType().toString
)将其转换为="OBJECT"
,然后返回,传递解析器的控制权返回杰克逊,杰克逊首先将Car.value1
设置为您的反序列化器返回的窗口,然后继续解析。
在这里有点奇怪。解串器返回后,Jackson期望有一个FIELD_NAME
令牌,并且由于您使用了START_OBJECT
令牌,因此它得到了一个。但是,它得到FIELD_NAME: "value1"
,并且由于Car
没有名为value1
和的任何属性,因此您已将Jackson配置为忽略未知属性,因此它跳过了该字段,并且值,然后移至FIELD_NAME: "value2"
,这将导致相同的行为。
现在剩余的令牌看起来像这样:
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"));
}