JAXB 编组:将空对象视为空对象
JAXB marshalling: treat empty object like it's null
我想用一个简单的例子来解释我的问题:
富:
@SomeXMLAnnotations
public class Foo {
// Bar is just a random class with its own XML annotations
@XmlElement(required = true)
Bar someBarObj;
boolean chosen = true;
boolean required = true;
public Foo(){
chosen = false;
}
public Foo(Bar someBarObj){
this.someBarObj = someBarObj;
}
}
我的班级:
@SomeXMLAnnotations
public class MyClass {
@XmlElement(required = false)
Foo anyFooObj;
@XmlElement(required = true)
Foo anyFooObjRequired;
public MyClass (){ }
public MyClass (Foo anyFooObj, Foo anyFooObjRequired){
this.anyFooObj = anyFooObj;
if(anyFooObj == null)
this.anyFooObj = new Foo();
/*
* This is the reason why i can't let 'anyFooObj' be 'null'.
* So 'anyFooObj' MUST be initialized somehow.
* It's needed for some internal logic, not JAXB.
*/
anyFooObj.required = false;
this.anyFooObjRequired = anyFooObjRequired;
}
}
示例对象:
Foo fooRequired = new Foo(new Bar());
MyClass myObj = new MyClass(null, fooRequired);
当我现在尝试编组 myObj
时,它会抛出这样的异常:
org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException;
cvc-complex-type.2.4.b: The content of element 'n0:anyFooObj ' is not complete.
One of '{"AnyNamespace":someBarObj}' is expected.
发生这种情况是因为 anyFooObj
已初始化,但它是必需的,成员
someBarObj
不是。
可能的解决方案:
我知道我可以将此方法添加到 MyClass
:
void beforeMarshal(Marshaller m){
if(! anyFooObj.chosen)
anyFooObj= null;
}
}
但是我有很多 类 而那些 类 有很多不需要的字段。
所以这个解决方案需要很长时间,而且看起来也不是一个合适的解决方案。
我的问题:
有没有办法告诉 JAXB 它应该像对待空对象一样对待空对象 null
?或者它应该在未正确设置时忽略元素。例如:
@XmlElement(required = false, ingnoreWhenNotMarshallable = true)
Foo anyFooObj;
注意:
我不是代码的开发者。我只需要将 JAXB 添加到项目中并使所有内容都与给定的 XSD 文件兼容。我不允许更改 类.
之间的关系
我认为您正试图让 JAXB 编组器做一些它实际上不是设计来做的事情,所以我想说您在这里进入了黑客领域。我建议推迟要求,首先尝试避免出现此问题。
就是说,如果您必须这样做,那么考虑到避免为每个 class/field 编写代码的要求,我认为您会希望为此使用反射 - 我在下面提供了一个示例反思性地检查所有字段的值。
有用的扩展是:
- 让它也考虑 getter 方法
- 通过要求该字段有一个额外的注释来选择加入 null 设置行为 - 您可以将其命名为@JAXBNullIfEmpty
Example.java:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringWriter;
import java.lang.reflect.Field;
public class Example
{
public abstract static class JAXBAutoNullifierForEmptyOptionalFields
{
void beforeMarshal(Marshaller x)
{
try
{
for (Field field : this.getClass().getFields())
{
final XmlElement el = field.getAnnotation(XmlElement.class);
// If this is an optional field, it has a value & it has no fields populated then we should replace it with null
if (!el.required())
{
if (JAXBAutoNullifierForEmptyOptionalFields.class.isAssignableFrom(field.getType()))
{
final JAXBAutoNullifierForEmptyOptionalFields val = (JAXBAutoNullifierForEmptyOptionalFields) field.get(
this);
if (val != null && !val.hasAnyElementFieldsPopulated())
field.set(this, null); // No fields populated, replace with null
}
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has all required fields: " + this, e);
}
}
boolean hasAnyElementFieldsPopulated()
{
for (Field field : this.getClass().getFields())
{
try
{
if (field.isAnnotationPresent(XmlElement.class))
{
// Retrieve value
final Object val = field.get(this);
// If the value is non-null then at least one field has been populated
if (val != null)
{
return true;
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has any populated JAXB fields: " + this, e);
}
}
// There were no fields with a non-null value
return false;
}
}
@XmlRootElement
public static class MyJAXBType extends JAXBAutoNullifierForEmptyOptionalFields
{
@XmlElement
public String someField;
@XmlElement
public MyJAXBType someOtherField;
public MyJAXBType()
{
}
public MyJAXBType(final String someField, MyJAXBType someOtherField)
{
this.someField = someField;
this.someOtherField = someOtherField;
}
}
public static void main(String[] args) throws Exception
{
final Marshaller marshaller = JAXBContext.newInstance(MyJAXBType.class).createMarshaller();
MyJAXBType innerValue = new MyJAXBType(); // Unpopulated inner value
MyJAXBType value = new MyJAXBType("some text value", innerValue);
final StringWriter sw = new StringWriter();
marshaller.marshal(value, sw); // Omits "someOtherField"
System.out.println(sw.toString());
}
}
我想用一个简单的例子来解释我的问题:
富:
@SomeXMLAnnotations
public class Foo {
// Bar is just a random class with its own XML annotations
@XmlElement(required = true)
Bar someBarObj;
boolean chosen = true;
boolean required = true;
public Foo(){
chosen = false;
}
public Foo(Bar someBarObj){
this.someBarObj = someBarObj;
}
}
我的班级:
@SomeXMLAnnotations
public class MyClass {
@XmlElement(required = false)
Foo anyFooObj;
@XmlElement(required = true)
Foo anyFooObjRequired;
public MyClass (){ }
public MyClass (Foo anyFooObj, Foo anyFooObjRequired){
this.anyFooObj = anyFooObj;
if(anyFooObj == null)
this.anyFooObj = new Foo();
/*
* This is the reason why i can't let 'anyFooObj' be 'null'.
* So 'anyFooObj' MUST be initialized somehow.
* It's needed for some internal logic, not JAXB.
*/
anyFooObj.required = false;
this.anyFooObjRequired = anyFooObjRequired;
}
}
示例对象:
Foo fooRequired = new Foo(new Bar());
MyClass myObj = new MyClass(null, fooRequired);
当我现在尝试编组 myObj
时,它会抛出这样的异常:
org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException;
cvc-complex-type.2.4.b: The content of element 'n0:anyFooObj ' is not complete.
One of '{"AnyNamespace":someBarObj}' is expected.
发生这种情况是因为 anyFooObj
已初始化,但它是必需的,成员
someBarObj
不是。
可能的解决方案:
我知道我可以将此方法添加到 MyClass
:
void beforeMarshal(Marshaller m){
if(! anyFooObj.chosen)
anyFooObj= null;
}
}
但是我有很多 类 而那些 类 有很多不需要的字段。 所以这个解决方案需要很长时间,而且看起来也不是一个合适的解决方案。
我的问题:
有没有办法告诉 JAXB 它应该像对待空对象一样对待空对象 null
?或者它应该在未正确设置时忽略元素。例如:
@XmlElement(required = false, ingnoreWhenNotMarshallable = true)
Foo anyFooObj;
注意:
我不是代码的开发者。我只需要将 JAXB 添加到项目中并使所有内容都与给定的 XSD 文件兼容。我不允许更改 类.
之间的关系我认为您正试图让 JAXB 编组器做一些它实际上不是设计来做的事情,所以我想说您在这里进入了黑客领域。我建议推迟要求,首先尝试避免出现此问题。
就是说,如果您必须这样做,那么考虑到避免为每个 class/field 编写代码的要求,我认为您会希望为此使用反射 - 我在下面提供了一个示例反思性地检查所有字段的值。
有用的扩展是:
- 让它也考虑 getter 方法
- 通过要求该字段有一个额外的注释来选择加入 null 设置行为 - 您可以将其命名为@JAXBNullIfEmpty
Example.java:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringWriter;
import java.lang.reflect.Field;
public class Example
{
public abstract static class JAXBAutoNullifierForEmptyOptionalFields
{
void beforeMarshal(Marshaller x)
{
try
{
for (Field field : this.getClass().getFields())
{
final XmlElement el = field.getAnnotation(XmlElement.class);
// If this is an optional field, it has a value & it has no fields populated then we should replace it with null
if (!el.required())
{
if (JAXBAutoNullifierForEmptyOptionalFields.class.isAssignableFrom(field.getType()))
{
final JAXBAutoNullifierForEmptyOptionalFields val = (JAXBAutoNullifierForEmptyOptionalFields) field.get(
this);
if (val != null && !val.hasAnyElementFieldsPopulated())
field.set(this, null); // No fields populated, replace with null
}
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has all required fields: " + this, e);
}
}
boolean hasAnyElementFieldsPopulated()
{
for (Field field : this.getClass().getFields())
{
try
{
if (field.isAnnotationPresent(XmlElement.class))
{
// Retrieve value
final Object val = field.get(this);
// If the value is non-null then at least one field has been populated
if (val != null)
{
return true;
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has any populated JAXB fields: " + this, e);
}
}
// There were no fields with a non-null value
return false;
}
}
@XmlRootElement
public static class MyJAXBType extends JAXBAutoNullifierForEmptyOptionalFields
{
@XmlElement
public String someField;
@XmlElement
public MyJAXBType someOtherField;
public MyJAXBType()
{
}
public MyJAXBType(final String someField, MyJAXBType someOtherField)
{
this.someField = someField;
this.someOtherField = someOtherField;
}
}
public static void main(String[] args) throws Exception
{
final Marshaller marshaller = JAXBContext.newInstance(MyJAXBType.class).createMarshaller();
MyJAXBType innerValue = new MyJAXBType(); // Unpopulated inner value
MyJAXBType value = new MyJAXBType("some text value", innerValue);
final StringWriter sw = new StringWriter();
marshaller.marshal(value, sw); // Omits "someOtherField"
System.out.println(sw.toString());
}
}