杰克逊 xml 将属性值映射到 属性

Jackson xml map attribute value to property

我正在与旧系统集成,需要将以下 xml 解析到我的对象中。我正在尝试使用 jackson 执行此操作,但我无法使映射正常工作。有人知道如何将以下 xml 映射到 pojo 吗?

@JacksonXmlRootElement(localName = "properties")
@Data
public class Example {
    private String token;
    private String affid;
    private String domain;
}

xml 示例:

<properties>
    <entry key="token">rent</entry>
    <entry key="affid">true</entry>
    <entry key="domain">checking</entry>
</properties>

我试过添加

@JacksonXmlProperty(isAttribute = true, localName = "key")

属性,但这当然行不通,而且我看不到另一种方法可以使它起作用。有什么想法吗?

我正在这样使用映射器...

ObjectMapper xmlMapper = new XmlMapper();
dto = xmlMapper.readValue(XML_STRING, Example .class);

我正在使用以下依赖项

compile('org.springframework.boot:spring-boot-starter-web')
runtime('org.springframework.boot:spring-boot-devtools')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
compile('org.apache.commons:commons-lang3:3.5')
compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml')
compile('com.squareup.okhttp3:okhttp:3.10.0')

Jackson我已经仔细看过了,好像没有办法做到这一点。不过,我会在这里分享我的解决方案,以防对其他人有用。

package com.example.config;

import com.example.dto.Example;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

public class Converter extends AbstractHttpMessageConverter<Example> {
    private static final XPath XPATH_INSTANCE = XPathFactory.newInstance().newXPath();
    private static final StringHttpMessageConverter MESSAGE_CONVERTER = new StringHttpMessageConverter();

    @Override
    protected boolean supports(Class<?> aClass) {
        return aClass == Example.class;
    }

    @Override
    protected Example readInternal(Class<? extends LongFormDTO> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        String responseString = MESSAGE_CONVERTER.read(String.class, httpInputMessage);
        Reader xmlInput = new StringReader(responseString);
        InputSource inputSource = new InputSource(xmlInput);
        Example dto = new Example();
        Node xml;

        try {
            xml  = (Node) XPATH_INSTANCE.evaluate("/properties", inputSource, XPathConstants.NODE);
        } catch (XPathExpressionException e) {
            log.error("Unable to parse  response", e);
            return dto;
        }

        log.info("processing populate application response={}", responseString);

        dto.setToken(getString("token", xml));
        dto.setAffid(getInt("affid", xml, 36));
        dto.domain(getString("domain", xml));

        xmlInput.close();
        return dto;
    }

    private String getString(String propName, Node xml, String defaultValue) {
        String xpath = String.format("//entry[@key='%s']/text()", propName);
        try {
            String value = (String) XPATH_INSTANCE.evaluate(xpath, xml, XPathConstants.STRING);
            return StringUtils.isEmpty(value) ? defaultValue : value;
        } catch (XPathExpressionException e) {
            log.error("Received error retrieving property={} from xml", propName, e);
        }
        return defaultValue;
    }

    private String getString(String propName, Node xml) {
        return getString(propName, xml, null);
    }

    private int getInt(String propName, Node xml, int defaultValue) {
        String stringValue = getString(propName, xml);
        if (!StringUtils.isEmpty(stringValue)) {
            try {
                return Integer.parseInt(stringValue);
            } catch (NumberFormatException e) {
                log.error("Attempted to parse value={} as integer but received error", stringValue, e);
            }
        }
        return defaultValue;
    }

    private int getInt(String propName, Node xml) {
        return getInt(propName, xml,0);
    }

    private boolean getBoolean(String propName, Node xml) {
        String stringValue = getString(propName, xml );
        return Boolean.valueOf(stringValue);
    }

    @Override
    protected void writeInternal(Example dto, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        throw new UnsupportedOperationException("Responses of type=" + MediaType.TEXT_PLAIN_VALUE + " are not supported");
    }
}

我选择将其隐藏在消息转换器中,这样我就不必再次查看它,但您可以在您认为合适的地方应用这些步骤。如果你选择这条路线,你将需要配置一个休息模板来使用这个转换器。如果不是,将 xml 缓存到 Node 对象中很重要,因为每次重新生成的成本都非常高。

package com.example.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Configuration
public class RestConfig { 
    @Bean
    @Primary
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }

    @Bean
    public RestTemplate restTemplateLe(RestTemplateBuilder builder) {
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        ExampleConverter exampleConverter = new ExampleConverter();
        exampleConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_PLAIN));
        messageConverters.add(exampleConverter);

        return builder.messageConverters(messageConverters)
                      .requestFactory(new OkHttp3ClientHttpRequestFactory())
                      .build();
    }
}

这确实有效。

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;

public class XmlParserDemo {
public static void main(String[] args) throws IOException, XMLStreamException {
    String xmlString = "<properties>\n" +
            "    <entry key=\"token\">rent</entry>\n" +
            "    <entry key=\"affid\">true</entry>\n" +
            "    <entry key=\"domain\">checking</entry>\n" +
            "</properties>";
    XMLStreamReader sr = null;
    sr = XMLInputFactory.newFactory().createXMLStreamReader(new StringReader(xmlString));
    sr.next();
    XmlMapper mapper = new XmlMapper();
    List<Entry> entries = mapper.readValue(sr, new TypeReference<List<Entry>>() {
    });
    sr.close();
    entries.forEach(e ->
            System.out.println(e.key + ":" + e.value));

}

public static class Entry {
    @JacksonXmlProperty(isAttribute = true, localName = "key")
    private String key;
    @JacksonXmlText
    private String value;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
}

输出为:

token:rent
affid:true
domain:checking