防止写入默认属性值 JAXB
Prevent writing default attribute values JAXB
我正在尝试编写 class 以便在 JAXB 中编写具有属性的元素。在这个 XML 中有一些默认值,无论它们是字符串、整数还是自定义 class 类型。
以下缩减示例:
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
// I want to not write this to the xml when it is 0
@XmlAttribute(name = "num")
private int number;
// I want to not write this when it is "default"
@XmlAttribute(name = "str")
private String str;
}
根据JAXB Avoid saving default values
我知道如果我不想写字符串,我可以修改 getters/setters 以写入 null 并在读取 null 时读取默认值。
但是,对于 int,我不确定该怎么做,因为除非特别更改它,否则它的值将始终为 0。
有更好的方法吗?我可以将内部数据类型更改为 String,然后在需要时转换它,但这有点混乱。
可以改成整数
private Integer number;
那么这个对象的值在没有实例化的时候就是null
使用 Integer
而不是原始 int
。将所有基本类型替换为对应的对象,然后您可以使用 NULL
.
根据字符串默认值,使用并修改getter
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "FIELD")
public class NullAttrs {
private Integer number;
private String str;
public void setNumber(Integer number) {
this.number = number;
}
@XmlAttribute(name = "num")
public Integer getNumber() {
return number;
}
public void setStr(String str) {
this.str = str;
}
@XmlAttribute(name = "str")
public String getStr() {
if (str != null && str.equalsIgnoreCase("default"))
return null;
else if (str == null)
return "default";
else
return str;
}
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(NullAttrs.class);
NullAttrs root = new NullAttrs();
root.setNumber(null);
root.setStr("default");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
本例中的结果为空 FIELD
:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FIELD/>
您可以通过将字段更改为默认空值不出现在 XML 表示中的对象类型来执行以下操作)并在 getter 中放置一些逻辑:
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
@XmlAttribute(name = "num")
private Integer number;
@XmlAttribute
private String str;
public int getNumber() {
if(null == number) {
return 0;
} else {
return number;
}
}
public void setNumber(int number) {
this.number = number;
}
public String getStr() {
if(null == str) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
}
}
允许取消设置 属性
如果你想让set操作return a 属性到它的默认状态那么你需要在set方法中添加逻辑。
public void setNumber(int number) {
if(0 == number) {
this.number = null;
} else {
this.number = number;
}
}
或者您可以提供一个未设置的方法:
public void unsetNumber() {
this.number = null;
}
允许设置为 null
如果你想允许 str
属性 被设置为 null
这样 get 方法将 return null
而不是 "default"
然后你可以维护一个标志来跟踪它是否已设置:
private strSet = false;
public String getStr() {
if(null == str && !strSet) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
this.strSet = true;
}
更新
Blaise, don't you think that the solution is pretty verbose?
是
I mean that such use case should be probably supported by framework.
For example using annotation like @DefaultValue.
JAXB 目前如何支持默认值
如果 XML 中没有节点,则不会对 Java 对象中相应的 field/property 执行集合。这意味着您将 属性 初始化为的任何值仍然存在。在编组上,因为值已填充,它将被编组出来。
真正要求的是什么
真正要求的是在 field/property 具有默认值时不封送它。通过这种方式,您希望 marshal 行为对于空值和默认值是相同的。这就引入了一些需要解决的问题:
- 您现在如何将 null 编组到 XML?默认情况下,它是否仍被编组为缺失节点?
- 是否需要提供一种机制来区分 属性 是默认值(不存在于 XML 中)和已设置为与默认值相同的值(存在于 XML 中) XML)?
人们今天在做什么?
通常对于此用例,人们只需将 int
属性 更改为 Integer
并将 null
设为默认值。我以前没有遇到过有人要求这种行为 String
。
虽然它不像人们希望的那样简洁,但可以创建 XmlAdapter 以避免编组默认值。
用例是这样的:
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
@XmlAttribute(name = "num")
@XmlJavaTypeAdapter(value = IntegerZero.class, type = int.class)
public int number;
@XmlAttribute(name = "str")
@XmlJavaTypeAdapter(StringDefault.class)
public String str = "default";
}
这是适配器。
整数零:
public class IntegerZero extends DefaultValue<Integer>
{
public Integer defaultValue() { return 0; }
}
字符串默认值:
public class StringDefault extends DefaultValue<String>
{
public String defaultValue() { return "default"; }
}
默认值适配器:
public class DefaultValue<T> extends XmlAdapter<T, T>
{
public T defaultValue() { return null; }
public T marshal(T value) throws Exception
{
return (value == null) || value.equals(defaultValue()) ? null : value;
}
public T unmarshal(T value) throws Exception
{
return value;
}
}
对于少量不同的默认值,此方法效果很好。
我发现使用自定义 getters/setters 或适配器的解决方案非常冗长,所以我选择了一个不同的解决方案:一个检查值并在默认情况下将其清空的编组器。
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Set;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.helpers.AbstractMarshallerImpl;
import javax.xml.transform.Result;
import com.google.common.collect.ImmutableSet;
class MyJaxbMarshaller extends AbstractMarshallerImpl {
/** See https://docs.oracle.com/cd/E13222_01/wls/docs103/webserv/data_types.html#wp221620 */
private static final Set<String> SUPPORTED_BASIC_TYPES = ImmutableSet.of(
"boolean", "java.lang.Boolean", "byte", "java.lang.Byte", "double", "java.lang.Double",
"float", "java.lang.Float", "long", "java.lang.Long", "int", "java.lang.Integer",
"javax.activation.DataHandler", "java.awt.Image", "java.lang.String",
"java.math.BigInteger", "java.math.BigDecimal", "java.net.URI", "java.util.Calendar",
"java.util.Date", "java.util.UUID", "javax.xml.datatype.XMLGregorianCalendar",
"javax.xml.datatype.Duration", "javax.xml.namespace.QName",
"javax.xml.transform.Source", "short", "java.lang.Short");
private final Marshaller delegate;
MyJaxbMarshaller(Marshaller delegate) {
this.delegate = delegate;
}
@Override
public void setProperty(String name, Object value) throws PropertyException {
super.setProperty(name, value);
delegate.setProperty(name, value);
}
@Override
public void marshal(Object jaxbElement, Result result) throws JAXBException {
try {
delegate.marshal(clearDefaults(jaxbElement), result);
} catch (ReflectiveOperationException ex) {
throw new JAXBException(ex);
}
}
private Object clearDefaults(Object element) throws ReflectiveOperationException {
if (element instanceof Collection) {
return clearDefaultsFromCollection((Collection<?>) element);
}
Class<?> clazz = element.getClass();
if (isSupportedBasicType(clazz)) {
return element;
}
Object adjusted = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
copyOrRemove(field, element, adjusted);
}
return adjusted;
}
private Object clearDefaultsFromCollection(Collection<?> collection)
throws ReflectiveOperationException {
@SuppressWarnings("unchecked")
Collection<Object> result = collection.getClass().getConstructor().newInstance();
for (Object element : collection) {
result.add(clearDefaults(element));
}
return result;
}
private static boolean isSupportedBasicType(Class<?> clazz) {
return SUPPORTED_BASIC_TYPES.contains(clazz.getName());
}
private void copyOrRemove(Field field, Object element, Object adjusted)
throws ReflectiveOperationException {
Object value = field.get(element);
if (value != null) {
if (value.equals(field.get(adjusted))) {
value = null;
} else {
value = clearDefaults(value);
}
}
field.set(adjusted, value);
}
}
这适用于 class 类
@XmlRootElement
public class Foo {
@XmlAttribute public Integer intAttr = 0;
@XmlAttribute public String strAttr = "default";
}
如果需要,您可以使其更加灵活,例如您可以使用注释来标记您希望在默认情况下忽略的属性,或者扩展 class 以了解 @XmlTransient
或方法访问器之类的东西(这两个都不是我的问题现在的项目)。
您为绑定的简单性付出的代价 classes 是编组器将创建您将要编组的对象的深层副本,并与默认值进行大量比较确定要消除的内容。因此,如果运行时性能对您来说是一个问题,那么这可能是不行的。
我正在尝试编写 class 以便在 JAXB 中编写具有属性的元素。在这个 XML 中有一些默认值,无论它们是字符串、整数还是自定义 class 类型。
以下缩减示例:
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
// I want to not write this to the xml when it is 0
@XmlAttribute(name = "num")
private int number;
// I want to not write this when it is "default"
@XmlAttribute(name = "str")
private String str;
}
根据JAXB Avoid saving default values 我知道如果我不想写字符串,我可以修改 getters/setters 以写入 null 并在读取 null 时读取默认值。
但是,对于 int,我不确定该怎么做,因为除非特别更改它,否则它的值将始终为 0。
有更好的方法吗?我可以将内部数据类型更改为 String,然后在需要时转换它,但这有点混乱。
可以改成整数
private Integer number;
那么这个对象的值在没有实例化的时候就是null
使用 Integer
而不是原始 int
。将所有基本类型替换为对应的对象,然后您可以使用 NULL
.
根据字符串默认值,使用并修改getter
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "FIELD")
public class NullAttrs {
private Integer number;
private String str;
public void setNumber(Integer number) {
this.number = number;
}
@XmlAttribute(name = "num")
public Integer getNumber() {
return number;
}
public void setStr(String str) {
this.str = str;
}
@XmlAttribute(name = "str")
public String getStr() {
if (str != null && str.equalsIgnoreCase("default"))
return null;
else if (str == null)
return "default";
else
return str;
}
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(NullAttrs.class);
NullAttrs root = new NullAttrs();
root.setNumber(null);
root.setStr("default");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
本例中的结果为空 FIELD
:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FIELD/>
您可以通过将字段更改为默认空值不出现在 XML 表示中的对象类型来执行以下操作)并在 getter 中放置一些逻辑:
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
@XmlAttribute(name = "num")
private Integer number;
@XmlAttribute
private String str;
public int getNumber() {
if(null == number) {
return 0;
} else {
return number;
}
}
public void setNumber(int number) {
this.number = number;
}
public String getStr() {
if(null == str) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
}
}
允许取消设置 属性
如果你想让set操作return a 属性到它的默认状态那么你需要在set方法中添加逻辑。
public void setNumber(int number) {
if(0 == number) {
this.number = null;
} else {
this.number = number;
}
}
或者您可以提供一个未设置的方法:
public void unsetNumber() {
this.number = null;
}
允许设置为 null
如果你想允许 str
属性 被设置为 null
这样 get 方法将 return null
而不是 "default"
然后你可以维护一个标志来跟踪它是否已设置:
private strSet = false;
public String getStr() {
if(null == str && !strSet) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
this.strSet = true;
}
更新
Blaise, don't you think that the solution is pretty verbose?
是
I mean that such use case should be probably supported by framework. For example using annotation like @DefaultValue.
JAXB 目前如何支持默认值
如果 XML 中没有节点,则不会对 Java 对象中相应的 field/property 执行集合。这意味着您将 属性 初始化为的任何值仍然存在。在编组上,因为值已填充,它将被编组出来。
真正要求的是什么
真正要求的是在 field/property 具有默认值时不封送它。通过这种方式,您希望 marshal 行为对于空值和默认值是相同的。这就引入了一些需要解决的问题:
- 您现在如何将 null 编组到 XML?默认情况下,它是否仍被编组为缺失节点?
- 是否需要提供一种机制来区分 属性 是默认值(不存在于 XML 中)和已设置为与默认值相同的值(存在于 XML 中) XML)?
人们今天在做什么?
通常对于此用例,人们只需将 int
属性 更改为 Integer
并将 null
设为默认值。我以前没有遇到过有人要求这种行为 String
。
虽然它不像人们希望的那样简洁,但可以创建 XmlAdapter 以避免编组默认值。
用例是这样的:
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
@XmlAttribute(name = "num")
@XmlJavaTypeAdapter(value = IntegerZero.class, type = int.class)
public int number;
@XmlAttribute(name = "str")
@XmlJavaTypeAdapter(StringDefault.class)
public String str = "default";
}
这是适配器。
整数零:
public class IntegerZero extends DefaultValue<Integer>
{
public Integer defaultValue() { return 0; }
}
字符串默认值:
public class StringDefault extends DefaultValue<String>
{
public String defaultValue() { return "default"; }
}
默认值适配器:
public class DefaultValue<T> extends XmlAdapter<T, T>
{
public T defaultValue() { return null; }
public T marshal(T value) throws Exception
{
return (value == null) || value.equals(defaultValue()) ? null : value;
}
public T unmarshal(T value) throws Exception
{
return value;
}
}
对于少量不同的默认值,此方法效果很好。
我发现使用自定义 getters/setters 或适配器的解决方案非常冗长,所以我选择了一个不同的解决方案:一个检查值并在默认情况下将其清空的编组器。
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Set;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.helpers.AbstractMarshallerImpl;
import javax.xml.transform.Result;
import com.google.common.collect.ImmutableSet;
class MyJaxbMarshaller extends AbstractMarshallerImpl {
/** See https://docs.oracle.com/cd/E13222_01/wls/docs103/webserv/data_types.html#wp221620 */
private static final Set<String> SUPPORTED_BASIC_TYPES = ImmutableSet.of(
"boolean", "java.lang.Boolean", "byte", "java.lang.Byte", "double", "java.lang.Double",
"float", "java.lang.Float", "long", "java.lang.Long", "int", "java.lang.Integer",
"javax.activation.DataHandler", "java.awt.Image", "java.lang.String",
"java.math.BigInteger", "java.math.BigDecimal", "java.net.URI", "java.util.Calendar",
"java.util.Date", "java.util.UUID", "javax.xml.datatype.XMLGregorianCalendar",
"javax.xml.datatype.Duration", "javax.xml.namespace.QName",
"javax.xml.transform.Source", "short", "java.lang.Short");
private final Marshaller delegate;
MyJaxbMarshaller(Marshaller delegate) {
this.delegate = delegate;
}
@Override
public void setProperty(String name, Object value) throws PropertyException {
super.setProperty(name, value);
delegate.setProperty(name, value);
}
@Override
public void marshal(Object jaxbElement, Result result) throws JAXBException {
try {
delegate.marshal(clearDefaults(jaxbElement), result);
} catch (ReflectiveOperationException ex) {
throw new JAXBException(ex);
}
}
private Object clearDefaults(Object element) throws ReflectiveOperationException {
if (element instanceof Collection) {
return clearDefaultsFromCollection((Collection<?>) element);
}
Class<?> clazz = element.getClass();
if (isSupportedBasicType(clazz)) {
return element;
}
Object adjusted = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
copyOrRemove(field, element, adjusted);
}
return adjusted;
}
private Object clearDefaultsFromCollection(Collection<?> collection)
throws ReflectiveOperationException {
@SuppressWarnings("unchecked")
Collection<Object> result = collection.getClass().getConstructor().newInstance();
for (Object element : collection) {
result.add(clearDefaults(element));
}
return result;
}
private static boolean isSupportedBasicType(Class<?> clazz) {
return SUPPORTED_BASIC_TYPES.contains(clazz.getName());
}
private void copyOrRemove(Field field, Object element, Object adjusted)
throws ReflectiveOperationException {
Object value = field.get(element);
if (value != null) {
if (value.equals(field.get(adjusted))) {
value = null;
} else {
value = clearDefaults(value);
}
}
field.set(adjusted, value);
}
}
这适用于 class 类
@XmlRootElement
public class Foo {
@XmlAttribute public Integer intAttr = 0;
@XmlAttribute public String strAttr = "default";
}
如果需要,您可以使其更加灵活,例如您可以使用注释来标记您希望在默认情况下忽略的属性,或者扩展 class 以了解 @XmlTransient
或方法访问器之类的东西(这两个都不是我的问题现在的项目)。
您为绑定的简单性付出的代价 classes 是编组器将创建您将要编组的对象的深层副本,并与默认值进行大量比较确定要消除的内容。因此,如果运行时性能对您来说是一个问题,那么这可能是不行的。