繁体   English   中英

我的CustomDeserializer类第二次在同一个类的第二个字段上使用时不起作用

[英]My CustomDeserializer class doesn't work second time that is used on second field of a same class

我正在使用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的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

您的反序列化器无法正常工作。

当你到达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"
    }
}

解析器将其标记为允许我们使用它。 让我们将此标记的状态表示为此令牌列表:

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由deserialised CustomDeserialize所以它创建了一个CustomDeserialize并调用它的deserialize方法。

然后JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);调用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

您的反序列化器通过传递field + nextField="windowSecondnull" )和jsonNode.getNodeType().toString="OBJECT" )将其转换为Window ,然后返回,将解析器的控制权传递回Jackson,它首先设置Car.value1到你的deserialiser返回的窗口,然后继续解析。

这里有点奇怪。 在您的反序列化程序返回后,Jackson期待一个FIELD_NAME令牌,因为您使用了START_OBJECT令牌,所以它获得了一个令牌。 但是,它获得了FIELD_NAME: "value1" ,因为Car没有任何名为value1属性, 并且您已将Jackson配置为忽略未知属性,它会跳过此字段并将其值移至FIELD_NAME: "value2"会导致相同的行为。

现在剩余的令牌看起来像这样:

END_OBJECT
END_OBJECT

下一个标记是END_OBJECT ,表示您的Car已正确反序列化,因此Jackson返回。

这里需要注意的是,解析器仍然有一个剩余的令牌,最后一个END_OBJECT 但由于默认情况下Jackson忽略了剩余的令牌 ,不会导致任何错误。

如果要查看它失败,请删除line 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"));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM