【问题标题】:How to alter the value of a JsonNode while constructing it from a string in Jackson如何在从杰克逊的字符串构造 JsonNode 时更改它的值
【发布时间】:2019-08-22 13:57:10
【问题描述】:

我有一个 JSON 字符串,我想在使用 Jackson 库构建 JsonNode 时更改该值。 例如:-

input: {"name":"xyz","price":"90.00"}
output:{"name":"xyz-3","price":90.90}

我创建了自己的 JsonFactory 并传递了自己的 Parser。但我只能更改键,不能更改与键关联的值。

代码:

private static ObjectMapper create() {
        ObjectMapper objectMapper = new ObjectMapper(new JsonFactory() {
            @Override
            protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException {
                return new MyParser(super._createParser(data, offset, len, ctxt));
            }

            @Override
            protected JsonParser _createParser(InputStream in, IOContext ctxt) throws IOException {
                return new MyParser(super._createParser(in, ctxt));
            }

            @Override
            protected JsonParser _createParser(Reader r, IOContext ctxt) throws IOException {
                return new MyParser(super._createParser(r, ctxt));
            }

            @Override
            protected JsonParser _createParser(char[] data, int offset, int len, IOContext ctxt, boolean recyclable)
                    throws IOException {
                return new MyParser(super._createParser(data, offset, len, ctxt, recyclable));
            }
        });

private static final class MyParser extends JsonParserDelegate {

        private MyParser(JsonParser d) {
            super(d);
        }

        @Override
        public String getCurrentName() throws IOException, JsonParseException {
            ....
        }

        @Override
        public String getText() throws IOException, JsonParseException {
           ...
        }

        @Override
        public Object getCurrentValue() {
            ...
        }


        @Override
        public String getValueAsString() throws IOException {
            ...
        }

        @Override
        public String getValueAsString(String defaultValue) throws IOException {
            ...
        }
    }

下面是从字符串构造 JsonNode 的代码。

mapper.readTree(jsonStr);

在这种情况下,当调用 readTree 方法时,不会调用 getCurrentValuegetValueAsString 方法,因此我无法在创建 JsonNode 本身时更改值。 json字符串也可以不同。基本上我想从字符串构造一个 JsonNode 。所以在这里绑定到一个特定的模式/bean 不是一个好的选择。 如何解决这个问题? TIA

为 2.7.4 版本添加更新代码:-

static class MyParser extends JsonParserDelegate {
        MyParser(final JsonParser delegate) {
            super(delegate);
        }

        @Override
        public String getText() throws IOException {
            final String text = super.getText();

            if ("name".equals(getCurrentName())) {
                return text + "-3";
            }

            return text;
        }

        @Override
        public JsonToken nextToken() throws IOException {
            if ("price".equals(getCurrentName())) {
                // Advance token anyway
                super.nextToken();
                return JsonToken.VALUE_NUMBER_FLOAT;
            }

            return super.nextToken();
        }

        @Override
        public int getCurrentTokenId() {
            try {
                if ("price".equals(getCurrentName())) {
                    return JsonTokenId.ID_NUMBER_FLOAT;
                }
            } catch (final IOException e) {
                //
            }

            return super.getCurrentTokenId();
        }

        @Override
        public NumberType getNumberType() throws IOException {
            if ("price".equals(getCurrentName())) {
                return NumberType.FLOAT;
            }

            return super.getNumberType();
        }

        @Override
        public float getFloatValue() throws IOException {
            return Float.parseFloat(getValueAsString("0")) + 0.09F;
        }

        @Override
        public double getDoubleValue() throws IOException {
            return Double.parseDouble(getValueAsString("0")) + 0.09D;
        }


    }

pom.xml:-

         <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>2.8.7</version>
            <!--<scope>test</scope>-->
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.8.7</version>
        </dependency>

【问题讨论】:

  • 在您的情况下,output 是什么? .我不确定这种工作应该在deserialisation 层上完成。您可以正常阅读并更改业务级别的值。如果要更改JSON,可以使用JSON Path

标签: java jackson jackson-databind


【解决方案1】:

编辑:2.7.*2.9.* 之间存在细微差别。
2.9.* 能够区分doublefloat

getDoubleValue()
getFloatValue()

2.7.* 只使用

getDoubleValue()

即使是ID_NUMBER_FLOAT 令牌。
因此,您需要决定是否要保持追溯兼容性。

您也可以像我在这里所做的那样覆盖两者。


这就是您自定义MyParser所需的全部内容

static class MyParser extends JsonParserDelegate {
    MyParser(final JsonParser delegate) {
        super(delegate);
    }

    @Override
    public String getText() throws IOException {
        final String text = super.getText();

        if ("name".equals(getCurrentName())) {
            return text + "-3";
        }

        return text;
    }

    @Override
    public JsonToken nextToken() throws IOException {
        if ("price".equals(getCurrentName())) {
            // Advance token anyway
            super.nextToken();
            return JsonToken.VALUE_NUMBER_FLOAT;
        }

        return super.nextToken();
    }

    @Override
    public int getCurrentTokenId() {
        try {
            if ("price".equals(getCurrentName())) {
                return JsonTokenId.ID_NUMBER_FLOAT;
            }
        } catch (final IOException e) {
            //
        }

        return super.getCurrentTokenId();
    }

    @Override
    public NumberType getNumberType() throws IOException {
        if ("price".equals(getCurrentName())) {
            return NumberType.FLOAT;
        }

        return super.getNumberType();
    }

    @Override
    public float getFloatValue() throws IOException {
        return Float.parseFloat(getValueAsString("0")) + 0.09F;
    }

    @Override
    public double getDoubleValue() throws IOException {
       return Double.parseDouble(getValueAsString("0")) + 0.09D;
    }
}

输出:{"name":"xyz-3","price":90.09}

您的代码看起来不错,并且已经过测试并且可以正常工作;)

【讨论】:

  • 在我的示例中,与价格相关的值在输出中是浮点数,在输入中是字符串。上述解决方案将在输出中返回字符串
  • @dks551 已更新。测试和工作。是否要将 0.09 添加到浮点值(无论如何添加)?
  • @dks551 有什么错误?我已经测试了十几次。
  • 线程“主”com.fasterxml.jackson.core.JsonParseException 中的异常:当前令牌 (VALUE_STRING) 不是数字,无法在 [Source: {"name":"xyz- 3","价格":"90.09"};行:1,列:26]
  • @dks551 已确认。是版本问题。如果可能的话,我会更新 2.7.4。我用 2.9.8 做到了
【解决方案2】:

您确定对于Separation of Concerns,在已解析数据中混合解析和更改是一个好主意吗?

如果您仍想这样做,您可以使用 Custom Deserializer 并按照您想要的方式处理您想要的字段名称和类型,例如:

class CustomDeserializer extends StdDeserializer<Entity> {
    public CustomDeserializer(Class<Entity> t) {
        super(t);
    }

    @Override
    public Entity deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
        String name = null;
        float price = 0;

        JsonToken currentToken = null;
        while ((currentToken = jp.nextValue()) != null) {
            switch (currentToken) {
                case VALUE_STRING:
                    switch (jp.getCurrentName()) {
                        case "name":
                            name = jp.getText() + "-3"; // change this text to whatever you want;
                            break;
                        case "price":
                            price = Float.parseFloat(jp.getText()); // parse
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
        }
        return new Entity(name, price);
    }
}

注册您的自定义反序列化器之后,它可以在您想要的任何对象映射器上运行:

    @Test
    public void customDeserialization() throws IOException {
        // given
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Entity.class, new CustomDeserializer(Entity.class));
        mapper.registerModule(module);

        // when
        Entity entity = mapper.readValue("{\"name\":\"xyz\",\"price\":\"90.00\"}", Entity.class);

        // then
        assertThat(entity.getName()).isEqualTo("xyz-3");
        assertThat(entity.getPrice()).isEqualTo(90f);
    }

【讨论】:

  • 这里的实体是什么?
  • Entity 只是一个例子,把它当作反映你的 JSON {"name":"xyz","price":"90.00"} 的类。更一般地说,StdDeserializer 相对于它从给定 JSON 生成的类来说是通用的。
  • 我不想这样,我的 json 字符串可以不同。基本上我想从字符串构造一个 JsonNode 。所以在这里绑定到一个特定的模式/bean不是一个好的选择。
  • 哦,这是基本信息,请将此添加到您的问题中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多