#概要
spring-bootでHTTPリクエストにXMLを受け取りJavaオブジェクトにデシリアライズする。
ソースコード
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>
##デシリアライズ
詳細は以下で後述するが、XMLでJavaのコレクションに対応する部分がXMLのroot直下にあるかどうかで、アノテーションの書き方が変わる。
root直下では無い場合
以下のようなXMLが送られてくる、と想定する。list
下に繰り返し要素が存在する。
<sampleRoot>
<list>
<sampleElement>
<id>1</id>
<value>v1</value>
</sampleElement>
<sampleElement>
<id>2</id>
<value>v2</value>
</sampleElement>
</list>
</sampleRoot>
この場合は以下のようなクラスを用意する。特にアノテーションも必要ない。
@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";
}
実行結果は以下のような感じになる。
xml body SampleRoot1(list=[SampleElement(id=1, value=v1), SampleElement(id=2, value=v2)])
root直下の場合
以下のようなXMLが送られてくる、と想定する。root直下に繰り返し要素が存在する。
<sampleRoot>
<sampleElement>
<id>1</id>
<value>v1</value>
</sampleElement>
<sampleElement>
<id>2</id>
<value>v2</value>
</sampleElement>
</sampleRoot>
この場合は、以下のように、コレクションに@JacksonXmlElementWrapper(useWrapping = false)
を付与する。
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;
}
ちゃんとドキュメント読んでないんで正確な説明では無いと思うが、useWrapping = false
でこのプロパティは何かの要素で囲まれるものではない、と指定していると思われる。
実行結果。
xml body SampleRoot2(sampleElement=[SampleElement(id=1, value=v1), SampleElement(id=2, value=v2)])
アノテーションが無いと以下のようなエラーになる。SampleElement
のインスタンスをList<SampleElement>
そのものにマッピングしようとして失敗している感じ。
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)
なお、XMLの構造によっては以下のように@JacksonXmlRootElement
や@JacksonXmlProperty
の指定が必要になるケースもあると思われる。また、ぐぐった感じではjacksonのバージョンにも左右されるようなので、そこは都度確認が必要と思われる。
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;
}