Значение атрибута карты Джексона xml для свойства

Я интегрируюсь со старой системой и мне нужно разобрать следующий xml в мой объект. Я пытаюсь сделать это с Джексоном, но не могу заставить работать карту. Кто-нибудь знает, как сопоставить следующий 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')

1 ответ

Это действительно работает.

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

Я тщательно просмотрел Джексона, и, похоже, нет способа сделать это. Тем не менее, я поделюсь своим решением здесь на случай, если оно пригодится кому-то еще.

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();
    }
}
Другие вопросы по тегам