3 votes

Comment modifier la valeur d'un JsonNode tout en le construisant à partir d'une chaîne de caractères dans Jackson ?

J'ai une chaîne JSON et je veux modifier la valeur lors de la construction du JsonNode en utilisant la bibliothèque Jackson. ex:-

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

J'ai créé ma propre JsonFactory et j'ai passé mon propre Parser, mais je ne peux modifier que les clés, pas les valeurs associées à une clé.

code :

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 {
            ...
        }
    }

Voici le code permettant de construire le JsonNode à partir de la chaîne de caractères.

mapper.readTree(jsonStr);

Dans ce cas, lorsque la méthode readTree est appelée, l'élément getCurrentValue o getValueAsString ne sont pas appelées, et je ne peux donc pas modifier la valeur lors de la création du JsonNode lui-même. De plus, les chaînes json peuvent être différentes. En fait, je veux construire un JsonNode à partir d'une chaîne de caractères, donc le fait d'être lié à un schéma/bean spécifique n'est pas un bon choix ici. Comment résoudre ce problème ? TIA

Ajout du code mis à jour pour la version 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>

2voto

LppEdd Points 1646

Edit : il y a une différence subtile entre 2.7.* y 2.9.* .
Tandis que 2.9.* est capable de faire la différence entre double y float avec

getDoubleValue()
getFloatValue()

au lieu de 2.7.* utilise uniquement

getDoubleValue()

même pour ID_NUMBER_FLOAT jetons.
Vous devez donc décider si vous souhaitez maintenir la rétro-compatibilité ou non.

Vous pouvez également ignorer les deux, comme je l'ai fait ici.


C'est tout ce dont vous avez besoin pour votre projet personnalisé 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;
    }
}

Sortie : {"name":"xyz-3","price":90.09}

Votre code semble correct, et il est testé et fonctionne ;)

0voto

mle Points 1591

Êtes-vous vraiment sûr qu'en ce qui concerne la Séparation des préoccupations est-ce une bonne idée de mélanger l'analyse et les changements dans les données analysées ?

Si vous souhaitez toujours procéder de la sorte, vous pouvez utiliser un Désérialiseur personnalisé et traiter les noms et types de champs souhaités comme vous le souhaitez, par exemple :

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);
    }
}

Et après enregistrer votre désérialiseur personnalisé il fonctionne avec n'importe quel mappeur d'objets :

    @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);
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X