EclipseLink Moxyを利用してJSONをPOJOにバインド

はじめに

2017/7/7にJSON-BがFinal Releaseになったので、もはやJSON-Bでいいんだけど、過去の自分の作業記録ということで。

EclipseLink Moxyを利用してJSONPOJOにバインドするサンプル。 JAXBを利用するので最初は違和感があったけど、慣れればそれほど気にならない。

ディレクトリ構成

.
|-- pom.xml
`-- src
    |-- main
    |   |-- java
    |   |   `-- redj
    |   |       `-- moxy
    |   |           |-- JaxbJson.java
    |   |           `-- binding
    |   |               |-- Person.java
    |   |               `-- Root.java
    |   `-- resources
    |       `-- META-INF
    |           `-- services
    |               `-- javax.xml.bind.JAXBContext
    `-- test
        |-- java
        |   `-- redj
        |       `-- moxy
        |           `-- JaxbJsonTest.java
        `-- resources
            `-- redj
                `-- moxy
                    `-- data.json

ソースコード

src/test/resources/redj/moxy/data.json

テストデータとして、以下のJSONを利用。

{
    "persons": [
        {
            "first-name": "Ichiro",
            "last-name": "Yamada",
            "age": 20
        },
        {
            "first-name": "Taro",
            "last-name": "Suzuki",
            "age": 30
        }
    ]
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>redj-moxy</groupId>
    <artifactId>redj-moxy</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
  
    <properties>
        <!-- configurations -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        
        <!-- dependency versions -->
        <junit.version>4.12</junit.version>
        <eclipselink.version>2.7.0</eclipselink.version>
    </properties>
        
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.moxy</artifactId>
            <version>${eclipselink.version}</version>
        </dependency>
    </dependencies>
</project>

src/main/java/redj/moxy/JaxbJson.java

JSON <=> POJOの変換を行うクラスで、ほぼJAXBのラッパー。 MarshallerとUnmarshallerにEclipseLink固有のプロパティを設定している点がポイント。

package redj.moxy;

import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;

public class JaxbJson {

    private final JAXBContext jaxbContext;

    public JaxbJson(Class<?>... classes) throws JAXBException {
        this.jaxbContext = JAXBContext.newInstance(classes);
    }

    public <T> T read(InputStream in, Class<T> clazz) throws JAXBException {
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        unmarshaller.setProperty("eclipselink.json.include-root", false);
        unmarshaller.setProperty("eclipselink.media-type", "application/json");
        JAXBElement<T> jaxbElement = (JAXBElement) unmarshaller.unmarshal(new StreamSource(in), clazz);

        return jaxbElement.getValue();
    }

    public void write(OutputStream out, Object object) throws JAXBException {
        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty("eclipselink.json.include-root", false);
        marshaller.setProperty("eclipselink.media-type", "application/json");
        marshaller.marshal(object, out);
    }
}

src/main/java/redj/moxy/binding/Root.java

テストデータのJSONのルートにあたるクラス。

package redj.moxy.binding;

import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Root {

    @XmlElement(name = "persons")
    public List<Person> persons;
}

src/main/java/redj/moxy/binding/Person.java

テストデータのJSONの配列personsの各要素にあたるクラス。

package redj.moxy.binding;

import javax.xml.bind.annotation.XmlElement;

public class Person {

    @XmlElement(name = "first-name")
    public String firstName;

    @XmlElement(name = "last-name")
    public String lastName;

    @XmlElement(name = "age")
    public int age;
}

src/main/resources/META-INF/services/javax.xml.bind.JAXBContext

JAXBの実装をEclipseLink Moxyに切り替えるためのファクトリクラスの指定。

org.eclipse.persistence.jaxb.JAXBContextFactory

src/test/java/redj/moxy/JaxbJsonTest.java

data.jsonPOJOに変換し、そのPOJOをもう一度JSONに変換して標準出力に出力するテスト。 テストの最後で改行を出力しているのは、単にMavenの出力の見た目を整えるため。

package redj.moxy;

import java.io.InputStream;
import org.junit.Test;
import redj.moxy.binding.Root;

public class JaxbJsonTest {

    @Test
    public void test() throws Exception {
        try (InputStream in = getClass().getClassLoader().getResourceAsStream("redj/moxy/data.json")) {
            JaxbJson jaxbJson = new JaxbJson(Root.class);
            Root root = jaxbJson.read(in, Root.class);
            jaxbJson.write(System.out, root);

            System.out.println();   // I want a newline !!
        }
    }
}

ビルドとテスト

$ mvn clean install
(略)

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running redj.moxy.JaxbJsonTest
{"persons":[{"first-name":"Ichiro","last-name":"Yamada","age":20},{"first-name":"Taro","last-name":"Suzuki","age":30}]}
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.741 sec

(略)

参考