[JAVA] Deserialize XML into a collection with spring-boot

Overview

Receive XML in HTTP request with spring-boot and deserialize it into Java object.

Source code

pom.xml

pom.xml


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

Deserialize

Details will be described later, but how to write annotations changes depending on whether the part corresponding to the Java collection in XML is directly under the root of XML.

If not directly under root

Suppose that the following XML is sent. There is a repeating element under list </ code>.

<sampleRoot>
	<list>
		<sampleElement>
			<id>1</id>
			<value>v1</value>
		</sampleElement>
		<sampleElement>
			<id>2</id>
			<value>v2</value>
		</sampleElement>
	</list>	
</sampleRoot>

In this case, prepare the following class. No special annotation is required.

@Data
public class SampleRoot1 {
  List<SampleElement> list;
}
@Data
public class SampleElement {
  String id;
  String value;
}
@RestController
public class SampleController {
  @PostMapping(value = "/hoge1")
  @RequestMapping(consumes = MediaType.TEXT_XML_VALUE)
  public String hoge1(@RequestBody SampleRoot1 s) {
    System.out.println("xml body " + s);
    return "ok";
  }

The execution result looks like the following.

xml body SampleRoot1(list=[SampleElement(id=1, value=v1), SampleElement(id=2, value=v2)])

Directly under root

Suppose that the following XML is sent. There is a repeating element directly under root.

<sampleRoot>
	<sampleElement>
		<id>1</id>
		<value>v1</value>
	</sampleElement>
	<sampleElement>
		<id>2</id>
		<value>v2</value>
	</sampleElement>
</sampleRoot>

In this case, add `@ JacksonXmlElementWrapper (useWrapping = false)` to the collection as shown below.

package jp.co.rakuten.rmsapi.order.notice.reciever.controller;

import java.util.List;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;

import lombok.Data;

@Data
public class SampleRoot2 {
  @JacksonXmlElementWrapper(useWrapping = false)
  List<SampleElement> sampleElement;
}

I haven't read the documentation properly, so I don't think it's an accurate explanation, but it seems that useWrapping = false specifies that this property is not surrounded by any element.

Execution result.

xml body SampleRoot2(sampleElement=[SampleElement(id=1, value=v1), SampleElement(id=2, value=v2)])

If there is no annotation, the following error will occur. Feeling that trying to map an instance of SampleElement </ code> to `List <SampleElement>` itself has failed.

Failed to resolve argument 0 of type 'jp.co.rakuten.rmsapi.order.notice.reciever.controller.SampleRoot2' 
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `jp.co.rakuten.rmsapi.order.notice.reciever.controller.SampleElement` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `jp.co.rakuten.rmsapi.order.notice.reciever.controller.SampleElement` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('1')
 at [Source: (PushbackInputStream); line: 3, column: 8](through reference chain: jp.co.rakuten.rmsapi.order.notice.reciever.controller.SampleRoot2["sampleElement"]->java.util.ArrayList[0])
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:241)
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:223)

Depending on the XML structure, it may be necessary to specify `@ JacksonXmlRootElement``` or `@ JacksonXmlProperty``` as shown below. Also, it seems that it depends on the version of jackson, so it seems that you need to check it each time.

import java.util.List;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;

import lombok.Data;

@Data
@JacksonXmlRootElement(localName = "SampleRoot")
public class SampleRoot2 {
  @JacksonXmlElementWrapper(useWrapping = false)
  @JacksonXmlProperty
  List<SampleElement> sampleElement;
}

Recommended Posts