Jackson 的动态根元素

Dynamic root element with Jackson

我目前正在处理一个项目,该项目处理(出于遗留原因)必须具有代表其类型的标签名称的元素。

基本上我有这个:

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

输出如下:

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

但预期会是:

<someType></someType>

@JsonRootName 似乎不能用于方法或属性。

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

我假设 NodeDocument 包含不止一个 属性。在这种情况下,您需要将自定义序列化程序与 BeanSerializerModifier 一起实现,它允许您序列化所有属性。下面的代码显示了完整的解决方案:

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
}

以上代码打印:

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

假设您可以轻松地 switch/configure 映射一个脏的且不可配置但仍然很简单的方法是覆盖 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;
    }
}

无论如何都不是完美的解决方案,可能并不适合所有情况,但作为一个选项的示例,可以根据情况进行更改。