Volodymyr Rudyi
Volodymyr Rudyi

Reputation: 648

Universal adapter for JAXB

Let's say I have class Person:

class Person{
  String firstName;
  String lastName;
  String email;
}

XML has format:

<person>
 <firstName value="asd" /> 
 <lastName value="bcd" />
 <email value="qwe" />
</person>

I can unmarshal/marshal this class using own XmlAdapter implementation for each of field FirstNameAdapter, LastNameAdapter, EmailAdapter. As you can see each field represented in similar way - field name as xml element and field value as element's attribute. Is it possible to create "universal" adapter to which I'll be able to transfer name of the field and it will extract value of that field ?

P.S. I know about MOXy JAXB implementation, but I'm wondering if it is possible by means of reference JAXB implementation.

Thanks!

Upvotes: 4

Views: 1413

Answers (2)

MvG
MvG

Reputation: 60858

You can use an XmlAdapter like this:

import java.io.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;

@XmlType
class Valued {
    @XmlAttribute(name="value")
    public String value;
}

class ValuedAdapter extends XmlAdapter<Valued, String> {
    public Valued marshal(String s) {
        Valued v = new Valued();
        v.value = s;
        return v;
    }
    public String unmarshal(Valued v) {
        return v.value;
    }
}

@XmlRootElement
class Person {

    @XmlJavaTypeAdapter(ValuedAdapter.class)
    @XmlElement
    String firstName;

    @XmlJavaTypeAdapter(ValuedAdapter.class)
    @XmlElement
    String lastName;

}

class SO12928971 {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        p.firstName = "John";
        p.lastName = "Doe";
        JAXBContext jc = JAXBContext.newInstance(Person.class);
        StringWriter sw = new StringWriter();
        jc.createMarshaller().marshal(p, sw);
        String xml = sw.toString();
        System.out.println(xml);
        StringReader sr = new StringReader(xml);
        p = (Person)jc.createUnmarshaller().unmarshal(sr);
        assert "John".equals(p.firstName);
        assert "Doe".equals(p.lastName);
    }
}

The idea here is that XML Schema and therefore also JAXB has a clear distinction between element names and content types, even though many documents have a clear one-to-one correspondence between these two. So in the above code, the type Valued describes something that has a value attribute, without regards for the element name. The members you want to serialize are annotated as @XmlElement with no name included in that annotation. So they will generate elements with a name derived from the name of the member. The @XmlJavaTypeAdapter annotation will cause the serializer to treat these members as if their types vere Valued. So that is what their XML content type will be.

The schema for the above code looks like this:

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="person" type="person"/>

  <xs:complexType name="person">
    <xs:sequence>
      <xs:element name="firstName" type="valued" minOccurs="0"/>
      <xs:element name="lastName" type="valued" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="valued">
    <xs:sequence/>
    <xs:attribute name="value" type="xs:string"/>
  </xs:complexType>
</xs:schema>

Upvotes: 5

bdoughan
bdoughan

Reputation: 148977

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

P.S. I know about MOXy JAXB implementation, but I'm wondering if it is possible by means of reference JAXB implementation.

For comparisons sake, I have added how this could be done with EclipseLink JAXB (MOXy)'s @XmlPath extension.

Person

package forum12928971;

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
class Person{

    @XmlPath("firstName/@value")
    String firstName;

    @XmlPath("lastName/@value")
    String lastName;

    @XmlPath("email/@value")
    String email;

}

jaxb.properties

To specify MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain model with the following entry:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo

package forum12928971;

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

public class Demo {
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Person.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum12928971/input.xml");
        Person person = (Person) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(person, System.out);
    }

}

input.xml/Output

<?xml version="1.0" encoding="UTF-8"?>
<person>
   <firstName value="asd"/>
   <lastName value="bcd"/>
   <email value="qwe"/>
</person>

Upvotes: -1

Related Questions