Reputation: 3
Using MOXy (or any other java XML framework) is it possible to perform the following marshalling and unmarshalling between this xml and object:
<teacher>
<field name="Age">30</field>
<field name="Name">Bob</field>
<field name="Course">Math</field>
</teacher>
public class Teacher {
Field age;
Field name;
Field course;
}
public class Field {
String name;
String value;
}
There are solutions that work for marshalling (annotating all fields with @XmlElement(name= "field")
) and there are some solutions that work for unmarshalling (@XmlPath("field[@name='Age']/text()")
.
Is there however a solution that works both ways, or an approach that will unmarshal and marshal XML between these two formats?
Upvotes: 0
Views: 74
Reputation: 3
This is an solution using MOXy
@Test
@SneakyThrows
void testMarshal() {
// Marshalling
Teacher teacher =
new Teacher(new Field("age", "30"), new Field("name", "Bob"), new Field("course", "Math"));
JAXBContext context = JAXBContext.newInstance(Teacher.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(teacher, System.out);
// Unmarshalling
String xml =
"""
<teacher>
<field name="age">30</field>
<field name="name">Bob</field>
<field name="course">Math</field>
</teacher>""";
Unmarshaller unmarshaller = context.createUnmarshaller();
Teacher unmarshalledTeacher = (Teacher) unmarshaller.unmarshal(new StringReader(xml));
System.out.printf("Unmarshalled: %s, %s, %s%n",
unmarshalledTeacher.getAge(),
unmarshalledTeacher.getName(),
unmarshalledTeacher.getCourse());
}
}
@XmlRootElement(name = "teacher")
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class Teacher {
@XmlPath("field[@name='age']")
private Field age;
@XmlPath("field[@name='name']")
private Field name;
@XmlPath("field[@name='course']")
private Field course;
}
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class Field {
@XmlAttribute(name = "name")
private String name;
@XmlValue
private String value;
void beforeMarshal(Marshaller m){
name = null;
}
}
Output:
<?xml version="1.0" encoding="UTF-8"?>
<teacher>
<field name="age">30</field>
<field name="name">Bob</field>
<field name="course">Math</field>
</teacher>
Unmarshalled: Field(name=age, value=30), Field(name=name, value=Bob), Field(name=course, value=Math)
The beforeMarshal()
is a dirty getaround for the fact that attributes are duplicated when marshalling. I made an issue here https://github.com/eclipse-ee4j/eclipselink/issues/2022
Upvotes: 0
Reputation: 476
Using JAXB's xjc
tool to generate JAXB classes from an XML Schema yields:
xjc -no-header teacher.xsd
Teacher.java
package generated;
import java.util.*;
import jakarta.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "field" })
@XmlRootElement(name = "teacher")
public class Teacher
{
@XmlElement(required = true)
protected List<Field> field;
public List<Field> getField()
{
if (field == null)
field = new ArrayList<>();
return this.field;
}
}
Field.java
package generated;
import jakarta.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "value" })
@XmlRootElement(name = "field")
public class Field
{
@XmlValue
protected String value;
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
@XmlAttribute(name = "name", required = true)
protected String name;
public String getName() { return name; }
public void setName(String value) { this.name = value; }
}
From this XML schema:
teacher.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
>
<xs:element name="teacher">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="field"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="field">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" use="required" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:schema>
To address the comment "I wish to not unmarshal my XML into a list of Field but into their own distinct "age", "name" and "course" variables.", here is a revised XML schema to inject properties for "age", "name" and "course":
teacher.xsd (revised)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ci="http://jaxb.dev.java.net/plugin/code-injector"
xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
jaxb:extensionBindingPrefixes="ci"
elementFormDefault="qualified"
>
<xs:element name="teacher">
<xs:complexType>
<xs:annotation>
<xs:appinfo>
<ci:code>
<![CDATA[
private java.util.Map<String,String> fieldMap;
public java.util.Map<String,String> getFieldMap()
{
if ( fieldMap == null )
{
fieldMap = new java.util.HashMap<>();
for ( Field field : getField() )
fieldMap.put(field.getName(), field.getValue());
}
return fieldMap;
}
public String getAge() { return getFieldMap().get("Age"); }
public void setAge(String value) { getFieldMap().put("Age", value); }
public String getName() { return getFieldMap().get("Name"); }
public void setName(String value) { getFieldMap().put("Name", value); }
public String getCourse() { return getFieldMap().get("Course"); }
public void setCourse(String value) { getFieldMap().put("Course", value); }
]]>
</ci:code>
</xs:appinfo>
</xs:annotation>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="field"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="field">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" use="required" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:schema>
To generate the revised Teacher
class, use:
xjc -no-header -extension -Xinject-code teacher.xsd
Teacher.java (revised)
package generated;
import java.util.*;
import jakarta.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "field" })
@XmlRootElement(name = "teacher")
public class Teacher
{
@XmlElement(required = true)
protected List<Field> field;
public List<Field> getField()
{
if (field == null)
field = new ArrayList<>();
return this.field;
}
private java.util.Map<String,String> fieldMap;
public java.util.Map<String,String> getFieldMap()
{
if ( fieldMap == null )
{
fieldMap = new java.util.HashMap<>();
for ( Field field : getField() )
fieldMap.put(field.getName(), field.getValue());
}
return fieldMap;
}
public String getAge() { return getFieldMap().get("Age"); }
public void setAge(String value) { getFieldMap().put("Age", value); }
public String getName() { return getFieldMap().get("Name"); }
public void setName(String value) { getFieldMap().put("Name", value); }
public String getCourse() { return getFieldMap().get("Course"); }
public void setCourse(String value) { getFieldMap().put("Course", value); }
}
Upvotes: 0
Reputation: 34421
Using a Powershell script with and Xml Linq
using assembly System.Xml.Linq
$inputFilename = 'c:\temp\test.xml'
$outputFilename = 'c:\temp\test1.xml'
class Teacher {
[int]$Age
[string]$Name
[string]$Course
}
class Field {
[string]$Name
[string]$Value
}
class Content {
$Teachers = [System.Collections.ArrayList]::new()
$Fields = [System.Collections.ArrayList]::new()
}
$xDoc = [System.Xml.Linq.XDocument]::Load($inputFilename)
$Contents = [Content]::new()
#Parse Xml to classes
foreach($xTeacher in $xDoc.Descendants('teacher'))
{
$teacher = [Teacher]::new()
$Contents.Teachers.Add($teacher) | out-null
foreach($xField in $xTeacher.Elements('field'))
{
$name = $xField.Attribute('name').Value
$value = $xField.Value
$field = [Field]::new()
$field.Name = $name
$field.Value = $value
$Contents.Fields.Add($field) | out-null
$teacher.$name = $value
}
}
#create new XMl File
$ident = '<teachers></teachers>'
$xDoc = [System.Xml.Linq.XDocument]::Parse($ident)
$xTeachers = $xDoc.Root
foreach($teacher in $Contents.Teachers)
{
$xTeacher = [System.Xml.Linq.XElement]::new([System.Xml.Linq.XName]::Get('teacher'))
$xTeachers.Add($xTeacher) | out-null
$field = [System.Xml.Linq.XElement]::new([System.Xml.Linq.XName]::Get('field'), $teacher.Age)
$name = [System.Xml.Linq.XAttribute]::new([System.Xml.Linq.Xname]::Get('name'), 'Age')
$field.Add($name) | out-null
$xTeacher.Add($field) | out-null
$field = [System.Xml.Linq.XElement]::new([System.Xml.Linq.XName]::Get('field'), $teacher.Name)
$name = [System.Xml.Linq.XAttribute]::new([System.Xml.Linq.Xname]::Get('name'), 'Name')
$field.Add($name) | out-null
$xTeacher.Add($field) | out-null
$field = [System.Xml.Linq.XElement]::new([System.Xml.Linq.XName]::Get('field'), $teacher.Course)
$name = [System.Xml.Linq.XAttribute]::new([System.Xml.Linq.Xname]::Get('name'), 'Course')
$field.Add($name) | out-null
$xTeacher.Add($field) | out-null
}
$xDoc.Save($outputFilename)
Here is the xml file
<teachers>
<teacher>
<field name="Age">30</field>
<field name="Name">Bob</field>
<field name="Course">Math</field>
</teacher>
</teachers>
Upvotes: 0