Dynamic root element with Jackson

淺唱寂寞╮ 提交于 2021-02-11 06:44:13

问题


I'm currently working on a project that deals with elements that (for legacy reasons) must have a tag name that represents their type.

Basically I have this:

@JsonRootName("node")
class NodeDocument {
    private String type;
}

Which outputs something like:

<node type="someType"></node>

But what's expected would be:

<someType></someType>

@JsonRootName doesn't seem to be usable on a method or attribute.

Even though there is SerializationConfig.withRooName() or custom serializers, I can't seem to find a way to define the root name with a dynamic value stored in the object itself.


回答1:


I assume NodeDocument contains more than just one property. In that case you need to implement custom serialiser together with BeanSerializerModifier which allow you to serialise all properties. Below code shows complete solution:

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.util.Objects;

public class XmlJacksonApp {

    public static void main(String... args) throws Exception {
        SimpleModule dynamicRootNameModule = new SimpleModule();
        dynamicRootNameModule.setSerializerModifier(new DynamicRootNameBeanSerializerModifier());

        XmlMapper mapper = XmlMapper.xmlBuilder()
                .enable(SerializationFeature.INDENT_OUTPUT)
                .addModule(dynamicRootNameModule)
                .build();
        NodeDocument element = new NodeDocument();
        element.setId(123);
        element.setName("Rick and Morty.doc");
        element.setType("sitcom");

        mapper.writeValue(System.out, element);
    }
}

class DynamicRootNameBeanSerializerModifier extends BeanSerializerModifier {
    @Override
    public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
        if (beanDesc.getBeanClass() == NodeDocument.class) {
            return new NodeDocumentJsonSerializer((JsonSerializer<NodeDocument>) serializer);
        }
        return super.modifySerializer(config, beanDesc, serializer);
    }
}

class NodeDocumentJsonSerializer extends JsonSerializer<NodeDocument> {
    private final JsonSerializer<NodeDocument> serializer;

    NodeDocumentJsonSerializer(JsonSerializer<NodeDocument> serializer) {
        this.serializer = Objects.requireNonNull(serializer);
    }

    @Override
    public void serialize(NodeDocument value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
        writeDynamicRootName(value.getType(), xmlGen);
        serializeProperties(value, gen, serializers);
        writeEndObject(xmlGen);
    }

    private void writeDynamicRootName(String rootName, ToXmlGenerator xmlGen) throws IOException {
        xmlGen.setNextName(new QName("", rootName));
        xmlGen.writeStartObject();
    }

    private void serializeProperties(NodeDocument value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        serializer.unwrappingSerializer(NameTransformer.NOP).serialize(value, gen, serializers);
    }

    private void writeEndObject(ToXmlGenerator xmlGen) throws IOException {
        xmlGen.writeEndObject();
    }
}

class NodeDocument {

    @JsonIgnore
    private String type;
    private int id;
    private String name;

    // getters, setters
}

Above code prints:

<sitcom>
  <id>123</id>
  <name>Rick and Morty.doc</name>
</sitcom>



回答2:


Assuming you can easily switch/configure mapper a dirty and not so configurable but still quite easy way would be to override XmlMapper like:

@SuppressWarnings("serial")
public class MyXmlMapper extends XmlMapper {
    @Override
    public String writeValueAsString(Object value) throws JsonProcessingException {
        String xml = super.writeValueAsString(value);
        if(value instanceof NodeDocument) {
            return xml.replaceAll("NodeDocument", ((NodeDocument)value).getType());
        }
        return xml;
    }
}

Not a perfect solution by any means and may not be suitable for all situations but as an example of one option as to be changed according to the situation.



来源:https://stackoverflow.com/questions/62930208/dynamic-root-element-with-jackson

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!