weixin_39789979
weixin_39789979
2020-11-30 19:51

ObjectMapper.valueToTree inconsistent with eg. ObjectMapper.writeValueAsString

It looks like that ObjectMapper.valueToTree ignores enabled features, and in general outputs something very different than methods which actually produce JSON, such as ObjectMapper.writeValueAsString.

For example, when serializing a BigDecimal with value 100, the methods which produce JSON will write a value of 100, whereas the valueToTree method will always write 1E+2. Even when the WRITE_BIGDECIMAL_AS_PLAIN feature is enabled (although even when that feature is disabled, the writeValueAsString writes 100, not 1E+2).

Simple JUnit test case which proves this:

 java
public class JacksonTreeTest {


    public void testValueToTreeInconsistency() throws Exception {
        ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);

        BigDecimal bigDecimal = new BigDecimal(100);
        JsonNode valueToTree = objectMapper.valueToTree(bigDecimal);

        String string = objectMapper.writeValueAsString(bigDecimal);
        JsonNode readTree = objectMapper.readTree(string);

        Assert.assertEquals(readTree, valueToTree);
    }
}

该提问来源于开源项目:FasterXML/jackson-core

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

8条回答

  • weixin_39643189 weixin_39643189 5月前

    Perhaps the differece here is that JsonNode with BigDecimal value is not written equivalently: conversion itself should make no modifications, and should simply produce a node with given BigDecimal value. But it sounds like writing that node out will not apply same normalization as direct write would, which seems wrong.

    点赞 评论 复制链接分享
  • weixin_39643189 weixin_39643189 5月前

    I can not reproduce the problem. Test case above is not valid, because line:

    
    JsonNode readTree = objectMapper.readTree(string);
    

    will produce IntNode for input 100; numeric nodes with different types are not considered equal.

    I tested 2.5.0-rc1 since issue does not indicate version used.

    It is possible there is a problem, but at this point I am not able to reproduce any.

    点赞 评论 复制链接分享
  • weixin_39789979 weixin_39789979 5月前

    Well, the readTree was only used to have two JsonNode objects to compare, but is not strictly necessary for this particular unit test, to prove the output is invalid. This will do, too:

     java
    
    public void testValueToTreeInconsistency() throws Exception {
        ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);
    
        BigDecimal bigDecimal = new BigDecimal(100);
        JsonNode valueToTree = objectMapper.valueToTree(bigDecimal);
    
        Assert.assertEquals("100", valueToTree.asText());
    }
    

    Which results in:

    
    org.junit.ComparisonFailure: 
    Expected :100
    Actual   :1E+2
    

    If instead objectMapper.writeValueAsString(bigDecimal); is used, it will pass (as that outputs 100 correctly).

    This is using Jackson 2.5.0-rc1 through Maven:

     xml
    <dependency>
        <groupid>com.fasterxml.jackson.core</groupid>
        <artifactid>jackson-core</artifactid>
        <version>2.5.0-rc1</version>
    </dependency>
    
    <dependency>
        <groupid>com.fasterxml.jackson.core</groupid>
        <artifactid>jackson-databind</artifactid>
        <version>2.5.0-rc1</version>
    </dependency>
    
    点赞 评论 复制链接分享
  • weixin_39643189 weixin_39643189 5月前

    Ok thank you -- I will try to see what gives. Equality is tricky part all in all (multiple levels of rules); and the fact that JSON token 100 would be read as int (or, more generally, one textual representation goes to just one internal one, with given settings), but multiple number types can be serialized as 100 makes it unlikely that it is possible to retain exact type round-trip.

    But I think there is also a simpler problem to solve here to improve things.

    点赞 评论 复制链接分享
  • weixin_39643189 weixin_39643189 5月前

    Ah! The reason I could not reproduce this was that I had:

     java
            ObjectMapper mapper = new ObjectMapper()
                    .enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
    

    instead of your test that used

     java
    new ObjectMapper().enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);
    

    and this makes the difference. I'll try to see if I can resolve the problem with this feature, but it is worth noting it is actually deprecated in 2.5, since JsonParser.Feature and JsonGenerator.Feature flag configuration is now better supported. So I would recommend using JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN instead, if you can upgrade to 2.5.0 (which was just released couple of days ago).

    点赞 评论 复制链接分享
  • weixin_39643189 weixin_39643189 5月前

    Unfortunately the fix for 2.4 is not straight-forward; and the reason for deprecating SerializationFeature was partly due to it being difficult to ensure it gets properly propagated.

    I will mark this as fixed, considering work-around of using JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN.

    点赞 评论 复制链接分享
  • weixin_39789979 weixin_39789979 5月前

    I am interested to see what test case you used specifically, if not the one I provided? I ran my test using Jackson 2.5.0 and JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN (instead of the now deprecated SerializationFeature), and my test still fails:

     java
    
    public void testValueToTreeInconsistency() throws Exception {
        ObjectMapper objectMapper = new ObjectMapper().enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
    
        BigDecimal bigDecimal = new BigDecimal(100);
        JsonNode valueToTree = objectMapper.valueToTree(bigDecimal);
    
        Assert.assertEquals("100", valueToTree.asText());
    }
    
    
    org.junit.ComparisonFailure: 
    Expected :100
    Actual   :1E+2
    

    So it seems to me this is still an issue: the feature JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN is ignored when using ObjectMapper#valueToTree.

    But perhaps I made an error in this test?

    点赞 评论 复制链接分享
  • weixin_39643189 weixin_39643189 5月前

    Test I added earlier is

    src/test/java/com/fasterxml/jackson/databind/node/TestNumberNodes.java

    and it passes for me. However, I notice you are using valueToTree.asText(). This probably fails because it has no access to generator you configure. In general, node.toString() is only useful for debugging and not for anything else. In test that passes, String representation is obtained using mapper.writeValueAsString().

    点赞 评论 复制链接分享

相关推荐