简体   繁体   English

具有 Jackson 的动态根元素

[英]Dynamic root element with Jackson

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. @JsonRootName似乎不适用于方法或属性。

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.即使有SerializationConfig.withRooName()或自定义序列化程序,我似乎也无法找到一种方法来使用存储在 object 本身中的动态值来定义根名称。

I assume NodeDocument contains more than just one property.我假设NodeDocument包含不止一个属性。 In that case you need to implement custom serialiser together with BeanSerializerModifier which allow you to serialise all properties.在这种情况下,您需要与允许您序列化所有属性的BeanSerializerModifier一起实现自定义序列化器。 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>

Assuming you can easily switch/configure mapper a dirty and not so configurable but still quite easy way would be to override XmlMapper like:假设您可以轻松地切换/配置映射器一个脏的并且不是那么可配置但仍然很简单的方法是覆盖XmlMapper ,例如:

@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.无论如何都不是一个完美的解决方案,并且可能不适合所有情况,但作为一个选项的示例,可以根据情况进行更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM