一、问题
我在使用XStream时,遇到一个很坑的问题,一旦属性值为null,这个属性对应的标签就不会输出,例如:
@XStreamAlias("Student")
public class Student {
@XStreamAlias("Age")
private Integer age;
@XStreamAlias("Name")
private String name;
}
public static void main(String[] args) {
Student stu = new Student();
stu.setAge(1);
stu.setName(null);
XStream xStream = new XStream();
// 使用注解
xStream.autodetectAnnotations(true);
// 不输出class信息
xStream.aliasSystemAttribute(null, "class");
System.out.println(xStream.toXML(stu));
}
输出结果:
<Student>
<Age>1</Age>
</Student>
咱们想要的结果却是如下:
<Student>
<Age>1</Age>
<Name></Name>
</Student>
二、解决方案
我找了很多方案,都是自己写一个Converter,但是这些自己写的converter往往出现很多不可预知的BUG。最终我的解决方案参考这个链接:https://stackoverflow.com/questions/13066360/xstream-serialize-null-values虽然还是自己写一个Converter,但是和XStream处理逻辑一样,并且可以输出null值对应的标签
解决思路:新建一个NullConverter继承ReflectionConverter这个类,重写doMarshal方法,先把原来ReflectionConverter里面的代码全部copy下来。原来的处理逻辑是,if字段为null,就不进行处理了,咱们可以加一个else,将空值的标签也输出。
以下是修改的内容(可以在ReflectionConverter中搜索new Object很快就可以找到这段代码):
new Object() {
{
final Map hiddenMappers = new HashMap();
for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext(); ) {
FieldInfo info = (FieldInfo) fieldIter.next();
if (info.value != null) {
//...省略了一段很长的代码
}
//新增的else代码!!!
else{
// 处理null值的标签也输出,最后的参数就是标签里的值,咱们这里输出空字符串
writeField(info.fieldName,null,info.type,info.definedIn,"");
}
}
通过注册这个NullConverter(需要将这个转换器的优先级调到最低)咱们就可以输出Null值对应的标签啦:
public static void main(String[] args) {
Student stu = new Student();
stu.setAge(1);
stu.setName(null);
XStream xStream = new XStream();
// 使用注解
xStream.autodetectAnnotations(true);
// 不输出class信息
xStream.aliasSystemAttribute(null, "class");
NullConverter nullConverter = new NullConverter(xStream.getMapper(), new SunUnsafeReflectionProvider());
// 将转换器注册到非常低的位置非常重要
xStream.registerConverter(nullConverter,XStream.PRIORITY_VERY_LOW);
System.out.println(xStream.toXML(stu));
}
输出结果
<Student>
<Age>1</Age>
<Name></Name>
</Student>
最后附上NullConverter全部的代码:
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.core.ReferencingMarshallingContext;
import com.thoughtworks.xstream.core.util.ArrayIterator;
import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
import java.lang.reflect.Field;
import java.util.*;
/**
* 在xstream中注册这个converter,可以输出值为null的标签
* 注意在使用的时候,记得将转换器注册到非常低的位置:xStream.registerConverter(nullConverter, XStream.PRIORITY_VERY_LOW);
* @author : yuxia
* @date : 2020/3/19
*/
public class NullConverter extends ReflectionConverter {
public NullConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
public NullConverter(Mapper mapper, ReflectionProvider reflectionProvider, Class type) {
super(mapper, reflectionProvider, type);
}
@Override
protected void doMarshal(final Object source, final HierarchicalStreamWriter writer,
final MarshallingContext context) {
final List fields = new ArrayList();
final Map defaultFieldDefinition = new HashMap();
final Class sourceType = source.getClass();
// Attributes might be preferred to child elements ...
reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
final Set writtenAttributes = new HashSet();
@Override
public void visit(String fieldName, Class type, Class definedIn, Object value) {
if (!mapper.shouldSerializeMember(definedIn, fieldName)) {
return;
}
if (!defaultFieldDefinition.containsKey(fieldName)) {
Class lookupType = source.getClass();
// See XSTR-457 and OmitFieldsTest
if (definedIn != sourceType
&& !mapper.shouldSerializeMember(lookupType, fieldName)) {
lookupType = definedIn;
}
defaultFieldDefinition.put(
fieldName, reflectionProvider.getField(lookupType, fieldName));
}
SingleValueConverter converter = mapper.getConverterFromItemType(
fieldName, type, definedIn);
if (converter != null) {
final String attribute = mapper.aliasForAttribute(mapper.serializedMember(
definedIn, fieldName));
if (value != null) {
if (writtenAttributes.contains(fieldName)) {
ConversionException exception =
new ConversionException("Cannot write field as attribute for object, attribute name already in use");
exception.add("field-name", fieldName);
exception.add("object-type", sourceType.getName());
throw exception;
}
final String str = converter.toString(value);
if (str != null) {
writer.addAttribute(attribute, str);
}
}
writtenAttributes.add(fieldName);
} else {
fields.add(new FieldInfo(fieldName, type, definedIn, value));
}
}
});
new Object() {
{
final Map hiddenMappers = new HashMap();
for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext(); ) {
FieldInfo info = (FieldInfo) fieldIter.next();
if (info.value != null) {
final Field defaultField = (Field) defaultFieldDefinition.get(info.fieldName);
Mapper.ImplicitCollectionMapping mapping = mapper
.getImplicitCollectionDefForFieldName(
defaultField.getDeclaringClass() == info.definedIn ? sourceType : info.definedIn,
info.fieldName);
if (mapping != null) {
Set mappings = (Set) hiddenMappers.get(info.fieldName);
if (mappings == null) {
mappings = new HashSet();
mappings.add(mapping);
hiddenMappers.put(info.fieldName, mappings);
} else {
if (!mappings.add(mapping)) {
mapping = null;
}
}
}
if (mapping != null) {
if (context instanceof ReferencingMarshallingContext) {
if (info.value != Collections.EMPTY_LIST
&& info.value != Collections.EMPTY_SET
&& info.value != Collections.EMPTY_MAP) {
ReferencingMarshallingContext refContext = (ReferencingMarshallingContext) context;
refContext.registerImplicit(info.value);
}
}
final boolean isCollection = info.value instanceof Collection;
final boolean isMap = info.value instanceof Map;
final boolean isEntry = isMap && mapping.getKeyFieldName() == null;
final boolean isArray = info.value.getClass().isArray();
for (Iterator iter = isArray
? new ArrayIterator(info.value)
: isCollection ? ((Collection) info.value).iterator() : isEntry
? ((Map) info.value).entrySet().iterator()
: ((Map) info.value).values().iterator(); iter.hasNext(); ) {
Object obj = iter.next();
final String itemName;
final Class itemType;
if (obj == null) {
itemType = Object.class;
itemName = mapper.serializedClass(null);
} else if (isEntry) {
final String entryName = mapping.getItemFieldName() != null
? mapping.getItemFieldName()
: mapper.serializedClass(Map.Entry.class);
Map.Entry entry = (Map.Entry) obj;
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, entryName, entry.getClass());
writeItem(entry.getKey(), context, writer);
writeItem(entry.getValue(), context, writer);
writer.endNode();
continue;
} else if (mapping.getItemFieldName() != null) {
itemType = mapping.getItemType();
itemName = mapping.getItemFieldName();
} else {
itemType = obj.getClass();
itemName = mapper.serializedClass(itemType);
}
writeField(
info.fieldName, itemName, itemType, info.definedIn, obj);
}
} else {
writeField(
info.fieldName, null, info.type, info.definedIn, info.value);
}
}else{
// 处理null值的标签也输出
writeField(info.fieldName,null,info.type,info.definedIn,"");
}
}
}
void writeField(String fieldName, String aliasName, Class fieldType,
Class definedIn, Object newObj) {
Class actualType = newObj != null ? newObj.getClass() : fieldType;
ExtendedHierarchicalStreamWriterHelper.startNode(writer, aliasName != null
? aliasName
: mapper.serializedMember(sourceType, fieldName), actualType);
if (newObj != null) {
Class defaultType = mapper.defaultImplementationOf(fieldType);
if (!actualType.equals(defaultType)) {
String serializedClassName = mapper.serializedClass(actualType);
if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
String attributeName = mapper.aliasForSystemAttribute("class");
if (attributeName != null) {
writer.addAttribute(attributeName, serializedClassName);
}
}
}
final Field defaultField = (Field) defaultFieldDefinition.get(fieldName);
if (defaultField.getDeclaringClass() != definedIn) {
String attributeName = mapper.aliasForSystemAttribute("defined-in");
if (attributeName != null) {
writer.addAttribute(
attributeName, mapper.serializedClass(definedIn));
}
}
Field field = reflectionProvider.getField(definedIn, fieldName);
marshallField(context, newObj, field);
}
writer.endNode();
}
void writeItem(Object item, MarshallingContext context,
HierarchicalStreamWriter writer) {
if (item == null) {
String name = mapper.serializedClass(null);
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, name, Mapper.Null.class);
writer.endNode();
} else {
String name = mapper.serializedClass(item.getClass());
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, name, item.getClass());
context.convertAnother(item);
writer.endNode();
}
}
};
}
private static class FieldInfo extends FieldLocation {
final Class type;
final Object value;
FieldInfo(final String fieldName, final Class type, final Class definedIn, final Object value) {
super(fieldName, definedIn);
this.type = type;
this.value = value;
}
}
private static class FieldLocation {
final String fieldName;
final Class definedIn;
FieldLocation(final String fieldName, final Class definedIn) {
this.fieldName = fieldName;
this.definedIn = definedIn;
}
@Override
public int hashCode() {
final int prime = 7;
int result = 1;
result = prime * result + (definedIn == null ? 0 : definedIn.getName().hashCode());
result = prime * result + (fieldName == null ? 0 : fieldName.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final NullConverter.FieldLocation other = (NullConverter.FieldLocation) obj;
if (definedIn != other.definedIn) {
return false;
}
if (fieldName == null) {
if (other.fieldName != null) {
return false;
}
} else if (!fieldName.equals(other.fieldName)) {
return false;
}
return true;
}
}
}
网友评论