Jackson JsonNode с пустым ключом элемента - PullRequest
0 голосов
/ 25 мая 2020

Я использую jackson-dataformat- xml (2.9) для синтаксического анализа XML в JsonNode, а затем анализирую его до JSON (XML очень динамичный c, поэтому я использую JsonNode вместо привязки к POJO. Например, имена 'elementName' и 'id' могут различаться).

Бывает, что на этапе анализа JSON один из ключей элемента является пустой строкой ("") .

XML:

<elementName>
      <id type="pid">abcdef123</id>
</elementName>

Лог синтаксического анализа c:

public Parser() {
        ObjectMapper jsonMapper = new ObjectMapper();
        XmlMapper xmlMapper = new XmlMapper(new XmlFactory(new WstxInputFactory()));
}

public InputStream parseXmlResponse(InputStream xmlStream) {
        InputStream stream = null;

        try {
            JsonNode node = xmlMapper.readTree(xmlStream);
            stream = new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return stream;
    }

Json:

Результат:

{
   "elementName": {
     "id": {
        "type": "pid",
        "": "abcdef123"
     }
   },
}

Ожидается:

{
   "elementName": {
     "id": {
        "type": "pid",
        "value": "abcdef123"
     }
   },
}

Моя идея состоит в том, чтобы найти всякий раз, когда у меня есть пустой ключ «», и заменить его на «значение». Либо при десериализации XML, либо во время сериализации JSON. Я попытался использовать сериализатор, фильтр по умолчанию, но у меня не получилось, что он работает красиво и лаконично.

Предложения приветствуются.

Спасибо за помощь.

Возможное решение:

Основываясь на предложении @shoek, я решил написать собственный сериализатор, чтобы избежать создания промежуточного объекта (ObjectNode) во время процесса.

edit: рефакторинг на основе того же решение, предложенное @ shoek.

public class CustomNode {
    private JsonNode jsonNode;

    public CustomNode(JsonNode jsonNode) {
        this.jsonNode = jsonNode;
    }

    public JsonNode getJsonNode() {
        return jsonNode;
    }
}

public class CustomObjectsResponseSerializer extends StdSerializer<CustomNode> {

    protected CustomObjectsResponseSerializer() {
        super(CustomNode.class);
    }

    @Override
    public void serialize(CustomNode node, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        convertObjectNode(node.getJsonNode(), jgen, provider);
    }

    private void convertObjectNode(JsonNode node, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        jgen.writeStartObject();
        for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
            String childName = it.next();
            JsonNode childNode = node.get(childName);
            // XML parser returns an empty string as value name. Replacing it with "value"
            if (Objects.equals("", childName)) {
                childName = "value";
            }

            if (childNode instanceof ArrayNode) {
                jgen.writeFieldName(childName);
                convertArrayNode(childNode, jgen, provider);
            } else if (childNode instanceof ObjectNode) {
                jgen.writeFieldName(childName);
                convertObjectNode(childNode, jgen, provider);
            } else {
                provider.defaultSerializeField(childName, childNode, jgen);
            }
        }
        jgen.writeEndObject();

    }

    private void convertArrayNode(JsonNode node, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        jgen.writeStartArray();
        for (Iterator<JsonNode> it = node.elements(); it.hasNext(); ) {
            JsonNode childNode = it.next();

            if (childNode instanceof ArrayNode) {
                convertArrayNode(childNode, jgen, provider);
            } else if (childNode instanceof ObjectNode) {
                convertObjectNode(childNode, jgen, provider);
            } else {
                provider.defaultSerializeValue(childNode, jgen);
            }
        }
        jgen.writeEndArray();
    }
}

Ответы [ 3 ]

1 голос
/ 29 мая 2020

Вы также можете просто обработать DOM JSON, пройти ко всем объектам и переименовать ключи, которые являются пустыми строками, в «значение» .

Состояние гонки : такой ключ может уже существовать, и его нельзя перезаписывать
(например, <id type="pid" value="existing">abcdef123</id>).

Использование:
(примечание: вы не должны молча подавлять исключение и возвращать значение null, но разрешите ему распространяться, чтобы вызывающий абонент мог решить поймать и применить журнал аварийного переключения c, если требуется)

public InputStream parseXmlResponse(InputStream xmlStream) throws IOException {
    JsonNode node = xmlMapper.readTree(xmlStream);
    postprocess(node);
    return new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));
}

Постобработка:

private void postprocess(JsonNode jsonNode) {

    if (jsonNode.isArray()) {
        ArrayNode array = (ArrayNode) jsonNode;
        Iterable<JsonNode> elements = () -> array.elements();

        // recursive post-processing
        for (JsonNode element : elements) {
            postprocess(element);
        }
    }
    if (jsonNode.isObject()) {
        ObjectNode object = (ObjectNode) jsonNode;
        Iterable<String> fieldNames = () -> object.fieldNames();

        // recursive post-processing
        for (String fieldName : fieldNames) {
            postprocess(object.get(fieldName));
        }
        // check if an attribute with empty string key exists, and rename it to 'value',
        // unless there already exists another non-null attribute named 'value' which
        // would be overwritten.
        JsonNode emptyKeyValue = object.get("");
        JsonNode existing = object.get("value");
        if (emptyKeyValue != null) {
            if (existing == null || existing.isNull()) {
                object.set("value", emptyKeyValue);
                object.remove("");
            } else {
                System.err.println("Skipping empty key value as a key named 'value' already exists.");
            }
        }
    }
}

Вывод: как и ожидалось.

{
   "elementName": {
     "id": {
        "type": "pid",
        "value": "abcdef123"
     }
   },
}

РЕДАКТИРОВАТЬ: соображения по производительности:

Я сделал тест с большим XML файлом (enwikiquote-20200520-pages-articles-multistream.xml, en.wikiquote XML dump, 498,4 МБ ), 100 раундов, со следующими измеренными временами (с использованием дельт с System.nanoTime()):

  • среднее чтение время (файл, SSD): 2870,96 мс
    (JsonNode node = xmlMapper.readTree(xmlStream);)
  • среднее постобработка время: 0,04 мс
    (postprocess(node);)
  • среднее запись время (память): 0,31 мс
    (new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));)

Это фраза Секция миллисекунды для построения дерева объектов из файла размером ~ 500 МБ - так что производительность отличная и никаких проблем.

0 голосов
/ 29 мая 2020

Версия сериализатора.

package com.example;

import java.io.IOException;
import java.util.Iterator;
import java.util.Objects;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

public class Stackoverflow62009220_B {
    public static void main(String[] args) throws JsonProcessingException {
        // see https://www.baeldung.com/jackson-call-default-serializer-from-custom-serializer

        convert("{\"elementName\":{\"id\":{\"type\":\"pid\",\"\":\"abcdef123\"}}}");

        // j =  {"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,5],"obj":{"a":"A","b":22}}
        // (simple json object)
        String j = "{\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,5],\"obj\":{\"a\":\"A\",\"b\":22}}";
        convert(j);

        // g = {"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,{"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,5],"obj":{"a":"A","b":22}}],"obj":{"":"is_empty_field","num":1,"str":"aa","null_val":null,"empty_val":"","array":[3,5],"obj":{"a":"A","b":22}}}
        // (includes an array containing object j, and an object j containing array)
        String g = " {\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,{\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,5],\"obj\":{\"a\":\"A\",\"b\":22}}],\"obj\":{\"\":\"is_empty_field\",\"num\":1,\"str\":\"aa\",\"null_val\":null,\"empty_val\":\"\",\"array\":[3,5],\"obj\":{\"a\":\"A\",\"b\":22}}}";
        convert(g);
    }

    private static void convert(String str) throws JsonProcessingException {
        JsonNode input = (new ObjectMapper()).readTree(str);
        System.out.println("in:");
        System.out.println(input);

        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        SimpleSerializers serializers = new SimpleSerializers();
        serializers.addSerializer(ObjectNode.class, new MyObjectNodeSerializer());
        module.setSerializers(serializers);
        mapper.registerModule(module);

        String output = mapper.writer().writeValueAsString(input);
        System.out.println("out:");
        System.out.println(output);
        System.out.println("----------");
    }
}

class MyObjectNodeSerializer extends StdSerializer<ObjectNode> {

    public MyObjectNodeSerializer() {
        super(ObjectNode.class);
    }

    public static MyObjectNodeSerializer create() {
        return new MyObjectNodeSerializer();
    }

    @Override
    public void serialize(ObjectNode value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();
        for (Iterator<String> it = value.fieldNames(); it.hasNext();) {
            String childName = it.next();
            JsonNode childNode = value.get(childName);

            if (Objects.equals("", childName)) {
                childName = "value";
            }

            if (childNode instanceof ArrayNode) {
                gen.writeFieldName(childName);
                MyArrayNodeSerializer.create().serialize((ArrayNode) childNode, gen, provider);
            } else if (childNode instanceof ObjectNode) {
                gen.writeFieldName(childName);
                this.serialize((ObjectNode) childNode, gen, provider);
            } else {
                provider.defaultSerializeField(childName, childNode, gen);
            }
        }
        gen.writeEndObject();
    }
}

class MyArrayNodeSerializer extends StdSerializer<ArrayNode> {

    public MyArrayNodeSerializer() {
        super(ArrayNode.class);
    }

    public static MyArrayNodeSerializer create() {
        return new MyArrayNodeSerializer();
    }

    @Override
    public void serialize(ArrayNode value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartArray();
        for (Iterator<JsonNode> it = value.elements(); it.hasNext();) {
            JsonNode childNode = it.next();
            if (childNode instanceof ArrayNode) {
                this.serialize((ArrayNode) childNode, gen, provider);
            } else if (childNode instanceof ObjectNode) {
                MyObjectNodeSerializer.create().serialize((ObjectNode) childNode, gen, provider);
            } else {
                provider.defaultSerializeValue(childNode, gen);
            }
        }
        gen.writeEndArray();
    }
}
0 голосов
/ 28 мая 2020

Копирование на новый ObjectNode может решить вашу проблему.

package com.example;

import java.util.Iterator;
import java.util.Objects;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ValueNode;

public class Stackoverflow62009220 {
    public static void main(String[] args) throws JsonProcessingException {
        convert("{\"elementName\":{\"id\":{\"type\":\"pid\",\"\":\"abcdef123\"}}}");

        convert("{\"array\":[1,99,3]}");

        convert("{\"complex-array\":[null, 1, [3,7,5], {\"type\":\"pid\",\"\":\"abcdef123\"}]}");
    }

    private static void convert(String str) throws JsonProcessingException {
        JsonNode input = (new ObjectMapper()).readTree(str);
        System.out.println("in:");
        System.out.println(input);

        ObjectMapper mapper = new ObjectMapper();

        ObjectNode obj = convertObjectNode(input, mapper);

        String output = mapper.writer().writeValueAsString(obj);
        System.out.println("out:");
        System.out.println(output);
        System.out.println("----------");
    }

    private static ArrayNode convertArrayNode(JsonNode current, ObjectMapper mapper) {
        ArrayNode to = mapper.createArrayNode();
        for (Iterator<JsonNode> it = current.elements(); it.hasNext();) {
            JsonNode childNode = it.next();

            if (childNode instanceof ValueNode) {
                to.add(childNode);
            } else if (childNode instanceof ArrayNode) {
                // recurse
                to.add(convertArrayNode(childNode, mapper));
            } else if (childNode instanceof ObjectNode) {
                to.add(convertObjectNode(childNode, mapper));
            }
        }
        return to;
    }

    private static ObjectNode convertObjectNode(JsonNode current, ObjectMapper mapper) {
        ObjectNode to = mapper.createObjectNode();
        for (Iterator<String> it = current.fieldNames(); it.hasNext();) {
            String childName = it.next();
            JsonNode childNode = current.get(childName);

            if (Objects.equals("", childName)) {
                childName = "value";
            }

            if (childNode instanceof ValueNode) {
                to.set(childName, childNode);
            } else if (childNode instanceof ArrayNode) {
                to.set(childName, convertArrayNode(childNode, mapper));
            } else if (childNode instanceof ObjectNode) {
                // recurse
                to.set(childName, convertObjectNode(childNode, mapper));
            }
        }
        return to;
    }
}

Результатом предыдущего кода будет:

in:
{"elementName":{"id":{"type":"pid","":"abcdef123"}}}
out:
{"elementName":{"id":{"type":"pid","value":"abcdef123"}}}
----------
in:
{"array":[1,99,3]}
out:
{"array":[1,99,3]}
----------
in:
{"complex-array":[null,1,[3,7,5],{"type":"pid","":"abcdef123"}]}
out:
{"complex-array":[null,1,[3,7,5],{"type":"pid","value":"abcdef123"}]}
----------

PS

Я не смог найдите способ использовать настраиваемый сериализатор (например, this ) для нетипизированного JsonNode. Если кто знает, опубликуйте свой ответ. Это может быть лучшим решением с точки зрения использования памяти / времени обработки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...