JAXB使い方メモ

JAXB（Java Architecture for XML Binding）の使い方メモ。

JAXB とは、 XML と Java オブジェクトを相互変換するための API 仕様のこと。
<del>Java SE6 からは標準ライブラリに組み込まれているので、特に jar を追加することなく使える。</del>
Java 11 より、 JAXB は標準ライブラリから削除されました（Java EE には引き続き含まれています）。

# Java SE 11 以降の環境で JAXB を使う
JAXB はもともと Java EE の一部だった。
一時は SE に入れられたが、 Java 11 で EE 系のクラスが SE から削除されたときに、一緒に SE から削除された。

SE 11 以降の環境で JAXB を使用したい場合は、ライブラリとして追加する必要がある。

2019 年現在の、JAXB の参照実装は [たぶんこれ](https://javaee.github.io/jaxb-v2/)。
ここの[ドキュメント](https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#deployment-maven-coordinates) を見た感じ、次のアーティファクトを依存関係に入れれば利用できる。

```gradle:build.gradle
dependencies {
    implementation "javax.xml.bind:jaxb-api:2.3.1"
    implementation "org.glassfish.jaxb:jaxb-runtime:2.3.1"
}
```

`javax.xml.bind:jaxb-api` が、 JAXB の API を含むアーティファクトで、
`org.glassfish.jaxb:jaxb-runtime` が JAXB の実装になる。

## Jakarta EE での利用
Java EE から Jakarta EE　に変わったことで、ライブラリやパッケージ名が変わった。

Jakarta EE を使う場合は次のアーティファクトを指定する。

```gradle:build.gradle
dependencies {
    implementation "jakarta.xml.bind:jakarta.xml.bind-api:4.0.0"
    implementation "com.sun.xml.bind:jaxb-impl:4.0.3"
}
```

ランタイムには Eclipse から提供されている互換実装(Compatible Implementation)を指定している。
参考：https://eclipse-ee4j.github.io/jaxb-ri/

:::note warn
本記事のコード例は、すべて Java EE のパッケージ名 (`javax.xml.bind...`) で記載している。
Jakarta EE 版を利用する場合は、適宜 `jakarta.xml.bind...` パッケージに読み替えること。
:::


# オブジェクト → XML
```java:変換元Javaクラス
public class Hoge {
     
    private int id;
    private String value;
     
    // Getter, Setter は省略
}
```

```java:変換処理
import javax.xml.bind.JAXB;
 
public class JAXBSample {
 
    public static void main(String[] args) {
        Hoge hoge = new Hoge();
        hoge.setId(10);
        hoge.setValue("hoge");
         
        JAXB.marshal(hoge, System.out);
    }
}
```

```xml:変換結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
    <id>10</id>
    <value>hoge</value>
</hoge>
```

# XML → オブジェクト
```java:変換処理
public static void main(String[] args) {
    String xml = "<?xml version=\"1.0\"?>"
               + "<hoge>"
               + "  <id>20</id>"
               + "  <value>hogehoge</value>"
               + "</hoge>";
     
    Hoge hoge = JAXB.unmarshal(new StringReader(xml), Hoge.class);
     
    System.out.println("id=" + hoge.getId() + ", value=" + hoge.getValue());
}
```

```text:出力結果
id=20, value=hogehoge
```

# アノテーションを使って調整する
## フィールドをタグの属性と紐付ける
```java:XmlAttributeアノテーションで属性と紐付ける
import javax.xml.bind.annotation.XmlAttribute;
 
public class Hoge {
     
    private int id;
    private String value;
     
    @XmlAttribute
    public int getId() {
        return id;
    }
     
    // 以下 Getter, Setter 省略
}
```

```xml:XML変換結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge id="10">
    <value>hoge</value>
</hoge>
```

`XmlAttribute` アノテーションを、属性と紐付けたいフィールドの __getterメソッド__ に付与する。

## タグと属性の名前を任意の値に変更する
```java:各アノテーションのnameで名前を指定する
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
 
public class Hoge {
     
    private int id;
    private String value;
     
    @XmlAttribute(name="hoge-id")
    public int getId() {
        return id;
    }
    @XmlElement(name="hoge-value")
    public String getValue() {
        return value;
    }
     
    // Setter メソッドは省略
}
```

```xml:XML変換結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge hoge-id="10">
    <hoge-value>hoge</hoge-value>
</hoge>
```

タグ名の場合は `XmlElement` アノテーションの `name` に、
属性名の場合は `XmlAttribute` アノテーションの `name` にそれぞれ設定したい名前を指定する。

## 要素の順序の決定
順序を決定するためには、 `XmlType` アノテーションの `propOrder` を付与する。

```java:順序の決定
@XmlType(propOrder={"one", "two", "three"})
public class Hoge {
    private String one = "1";
    private String three = "3";
    private String two = "2";

    // Getter, Setter は省略
}
```

```xml:XML変換結果（デフォルト）
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
    <one>1</one>
    <two>2</two>
    <three>3</three>
</hoge>
```

※ [toenobu](http://qiita.com/toenobu) さんに追記していただきました。ありがとうございます。

## ルートタグの名前を変更する
```java:XmlRootElementアノテーションでルートタグの名前を変更する
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement(name="hoge-tag")
public class Hoge {
    private int id;
    private String value;
     
    // Getter, Setter 省略
}
```

```xml:XML変換結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge-tag>
    <id>10</id>
    <value>hoge</value>
</hoge-tag>
```

XML のルートタグとなるクラスに `XmlRootElement` アノテーションを付与し、 `name` に設定したいタグの名前を指定する。

## Listの変換をいい感じにする
`List<String>` のような `List` のフィールドを XML に変換すると、デフォルトだと以下のようになる。

```java:Listフィールドを持つクラス
public class Hoge {
    private List<String> listValue;
     
    // Getter, Setter は省略
}
```

```xml:XML変換結果（デフォルト）
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
    <listValue>aaa</listValue>
    <listValue>bbb</listValue>
    <listValue>ccc</listValue>
</hoge>
```

`List` の中身が同じ高さで出力されて格好悪い。

できれば以下のようにしたい。

```xml:XML変換結果（希望形）
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
    <list>
        <value>aaa</value>
        <value>bbb</value>
        <value>ccc</value>
    </list>
</hoge>
```

上記のように変換するためには、次のように Java クラスでアノテーションを設定する。

```java:希望形で変換させるためのアノテーション設定
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
 
public class Hoge {
    private List<String> listValue;
     
    @XmlElementWrapper(name="list")
    @XmlElement(name="value")
    public List<String> getListValue() {
        return listValue;
    }
    // Setter 省略
}
```

`XmlElementWrapper` アノテーションの `name` に `List` の各要素タグをラップするタグの名前を、
`XmlElement` アノテーションの `name` に要素タグの名前を設定する。

## 特定のフィールドを変換対象外にする
```java:XmlTransientアノテーションで対象外を指定する
import javax.xml.bind.annotation.XmlTransient;
 
public class Hoge {
     
    private int id;
    private String value;
     
    @XmlTransient
    public String getValue() {
        return value;
    }
     
    // Getter, Setter 省略
}
```

```xml:XML変換結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
    <id>10</id>
</hoge>
```

`XmlTransient` アノテーションを付与したフィールドは XML 変換対象外になる。

## enum をマッピングする
```java:MyClass
package jaxb;

public class MyClass {
    
    private MyEnum hoge = MyEnum.HOGE;
    private MyEnum fuga = MyEnum.FUGA;
    private MyEnum piyo = MyEnum.PIYO;
    
    // getter, setter 省略
}
```

```java:MyEnum
package jaxb;

import javax.xml.bind.annotation.XmlEnumValue;

public enum MyEnum {
    HOGE,
    @XmlEnumValue("fuga") FUGA,
    @XmlEnumValue("Piyo") PIYO,
}
```

```xml:XML変換結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myClass>
    <fuga>fuga</fuga>
    <hoge>HOGE</hoge>
    <piyo>Piyo</piyo>
</myClass>
```

enum は、デフォルトでは列挙子の名前を文字列にした値とマッピングされる（HOGE）。

任意の値とマッピングさせたい場合は、 enum の列挙子に対して `@XmlEnumValue` を付与し、 value にマッピングしたい文字列を指定する（fuga, Piyo）。

## １つのインターフェースフィールドに対して、複数の実装クラスをマッピングする
```java:MyInterface.java
package jaxb;

public interface MyInterface {
    void hello();
}
```

```java:Hoge.java
package jaxb;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;

public class Hoge {
    
    private List<MyInterface> list;
    private MyInterface any;

    @XmlElementWrapper(name="list")
    @XmlElements({
        @XmlElement(name="fuga", type=Fuga.class),
        @XmlElement(name="piyo", type=Piyo.class)
    })
    public List<MyInterface> getList() {
        return this.list;
    }

    @XmlElements({
        @XmlElement(name="fuga", type=Fuga.class),
        @XmlElement(name="piyo", type=Piyo.class)
    })
    public MyInterface getAny() {
        return this.any;
    }

    // setter 省略
}
```

```java:Fuga.java
package jaxb;

public class Fuga implements MyInterface {
    
    private int num;

    @Override
    public void hello() {
        System.out.println("fuga : num = " + this.num);
    }

    // setter, getter 省略
}
```

```java:Piyo.java
package jaxb;

import javax.xml.bind.annotation.XmlAttribute;

public class Piyo implements MyInterface {

    private String value;
    
    @Override
    public void hello() {
        System.out.println("hoge : value = " + this.value);
    }

    @XmlAttribute
    public String getValue() {
        return this.value;
    }
    
    // setter 省略
}
```

`MyInterface` インターフェースを実装した `Fuga` クラスと `Piyo` クラスがある。

`Hoge` クラスでは `MyInterface` 型でフィールドを定義して、 `@XmlElements({@XmlElement(...), ...})` という形でタグ名と実装クラスのマッピングを定義している。

上記クラスは、↓のような XML に変換できる。

```xml:test.xml
<?xml version="1.0" encoding="UTF-8"?>
<hoge>
  <fuga>
    <num>123</num>
  </fuga>
  
  <list>
    <fuga>
      <num>543</num>
    </fuga>
    <piyo value="piyopiyo" />
  </list>
</hoge>
```

↑の XML を読み込んで、 `MyInterface#hello()` を実行すると、↓のようになる。

```java:JaxbMain.java
package jaxb;

import java.io.File;
import javax.xml.bind.JAXB;

public class JaxbMain {
    
    public static void main(String[] args) {
        Hoge hoge = JAXB.unmarshal(new File("test.xml"), Hoge.class);
        
        hoge.getAny().hello();
        System.out.println();
        
        for (MyInterface any : hoge.getList()) {
            any.hello();
        }
    }
}
```

```text:実行結果
fuga : num = 123

fuga : num = 543
hoge : value = piyopiyo
```

## 名前空間が宣言されている XML をマッピングする
次のように名前空間が宣言されている XML をマッピングする。

```xml:XML
<?xml version="1.0" encoding="UTF-8"?>
<entity xmlns="hoge-namespace" xmlns:fuga="fuga-namespace">
  <id>20</id>
  <fuga:value>VALUE</fuga:value>
</entity>
```

### フィールドごとに名前空間を設定する
```java:Entity.java
package sample.jaxb;

import javax.xml.bind.annotation.XmlElement;

public class Entity {
    
    private int id;
    private String value;
    
    @XmlElement(namespace="hoge-namespace")
    public int getId() {
        return this.id;
    }

    @XmlElement(namespace="fuga-namespace")
    public String getValue() {
        return this.value;
    }

    // setter 省略
}
```

`@XmlElement` アノテーションの `namespace` 属性で名前空間を指定する。

※`namespace` 属性は、 `@XmlAttribute` アノテーションにもある。

### `package-info.java` で共通の名前空間を設定する
`package-info.java` に `@XmlSchema` アノテーションを付けることで、パッケージ内に存在する全てのクラスに対して名前空間を一括で設定できる。

```java:package-info.java
@XmlSchema(
    namespace="hoge-namespace",
    elementFormDefault=XmlNsForm.QUALIFIED
)
package sample.jaxb;

import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlNsForm;
```

```java:Entity.java
package sample.jaxb;

import javax.xml.bind.annotation.XmlElement;

public class Entity {
    
    private int id;
    private String value;

    @XmlElement(namespace="fuga-namespace")
    public String getValue() {
        return this.value;
    }

    // getter, setter 省略
}
```

`hoge-namespace` は `package-info.java` の `@XmlSchema` アノテーションで指定しているので、 `Entity.java` では名前空間の指定を省略できる。

ある名前空間に属するタグが複数存在する場合は、この方法を使うと設定が１カ所にまとめられて楽になる。

ただし、 `package-info.java` で指定した設定が適用されるのは、そのパッケージ直下のクラスだけなので注意。


### Java オブジェクトから XML に変換するときの QName のプレフィックスを指定する
名前空間が設定された状態で Java オブジェクトを XML に変換すると、以下のように QName （qualified name＝修飾された名前）のプレフィックスに適当な値が使用される。

```java:Entity.java
package sample.jaxb;

import javax.xml.bind.annotation.XmlElement;

public class Entity {
    
    private int id;
    private String value;
    
    @XmlElement(namespace="hoge-namespace")
    public int getId() {
        return this.id;
    }

    @XmlElement(namespace="fuga-namespace")
    public String getValue() {
        return this.value;
    }

    // setter 省略
}
```

```java:JaxbMain.java
package sample.jaxb;

import javax.xml.bind.JAXB;


public class JaxbMain {
    
    public static void main(String[] args) {
        Entity entity = new Entity();
        entity.setId(12);
        entity.setValue("ENTITY");
        
        JAXB.marshal(entity, System.out);
    }
}
```

```xml:実行結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<entity xmlns:ns2="fuga-namespace" xmlns:ns3="hoge-namespace">
    <ns3:id>12</ns3:id>
    <ns2:value>ENTITY</ns2:value>
</entity>
```

`ns2`、 `ns3` という適当なプレフィックスが設定されている。

プレフィックスを任意の値にしたい場合は、 `package-info.java` の `@XmlSchema` アノテーションで以下のように設定する。

```java:package-info.java
@XmlSchema(
    xmlns={
        @XmlNs(prefix="hoge", namespaceURI="hoge-namespace"),
        @XmlNs(prefix="fuga", namespaceURI="fuga-namespace")
    }
)
package sample.jaxb;

import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlNs;
```

```xml:変換結果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<entity xmlns:fuga="fuga-namespace" xmlns:hoge="hoge-namespace">
    <hoge:id>12</hoge:id>
    <fuga:value>ENTITY</fuga:value>
</entity>
```

プレフィックスが `hoge` と `fuga` に変わっている。

# 参考
- [JAXBの簡単な解説（１） | Akira Koyasus WebLog](http://www.akirakoyasu.net/2011/02/27/251/)
- [JAXBの簡単な解説（２） | Akira Koyasus WebLog](http://www.akirakoyasu.net/2011/11/20/simple-tutorial-of-jaxb-2/)
- [Java技術最前線（第73回 JAXB その1～第80回 JAXB その8） - Java技術最前線：ITpro](http://itpro.nikkeibp.co.jp/article/COLUMN/20060915/248243/?ST=develop)
- [XML名前空間の簡単な説明](http://www.kanzaki.com/docs/sw/names.html)
- [JAXBと名前空間と - Starlight](http://d.hatena.ne.jp/Kazuhira/20120716/1342441007)