pom依赖
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.4</version>
</dependency>
工具类
自定义转换器,重写部分源码,解决空值问题
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 lombok.SneakyThrows;
import javax.xml.bind.annotation.XmlElement;
import java.lang.reflect.Field;
import java.util.*;
public class NillableReflectionConverter extends ReflectionConverter {
private transient ReflectionProvider pureJavaReflectionProvider;
public NillableReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
@Override
protected void doMarshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
final List fields = new ArrayList();
final Map defaultFieldDefinition = new HashMap();
reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
final Set writtenAttributes = new HashSet();
@SneakyThrows
@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();
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)) {
throw new ConversionException("Cannot write field with name '" + fieldName
+ "' twice as attribute for object of type " + source.getClass().getName());
}
final String str = converter.toString(value);
if (str != null) {
writer.addAttribute(attribute, str);
}
}
writtenAttributes.add(fieldName);
} else {
Field field = source.getClass().getDeclaredField(fieldName);
XmlElement xmlElement = field.getAnnotation(XmlElement.class);
boolean nillable = false;
if (xmlElement != null) {
nillable = xmlElement.nillable();
}
fields.add(new FieldInfo(fieldName, type, definedIn, value, nillable));
}
}
});
new Object() {
{
for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext(); ) {
FieldInfo info = (FieldInfo) fieldIter.next();
if (info.value == null) {
if (info.nillable) {
writeField(
info.fieldName, null, info.type, info.definedIn, "");
}
} else {
Mapper.ImplicitCollectionMapping mapping = mapper
.getImplicitCollectionDefForFieldName(
source.getClass(), info.fieldName);
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);
}
}
}
}
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(source.getClass(), 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 {
final String fieldName;
final Class type;
final Class definedIn;
final Object value;
final boolean nillable;
FieldInfo(String fieldName, Class type, Class definedIn, Object value, boolean nillable) {
this.fieldName = fieldName;
this.type = type;
this.definedIn = definedIn;
this.value = value;
this.nillable = nillable;
}
}
}
XmlUtil
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class XmlUtil {
public static String toXml(Object obj) {
XStream xStream = new XStream();
xStream.autodetectAnnotations(true);
xStream.registerConverter(new NillableReflectionConverter(xStream.getMapper(), new Sun14ReflectionProvider()), XStream.PRIORITY_VERY_LOW);
return xStream.toXML(obj);
}
public static <T> T fromXml(String xml, Class<T> targetClazz) {
XStream xStream = new XStream();
xStream.autodetectAnnotations(true);
xStream.processAnnotations(targetClazz);
return (T) xStream.fromXML(xml);
}
}
使用示例
Schedule
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Data;
import javax.xml.bind.annotation.XmlElement;
import java.util.List;
@Data
@XStreamAlias("Schedule")
public class Schedule {
private String id;
private String eventType;
@XStreamAlias("videoInputChannelID")
private String videoInputChannelId;
@XmlElement(nillable = true)
@XStreamAlias("TimeBlockList")
private List<TimeBlock> timeBlockList;
@XmlElement(nillable = true)
@XStreamAlias("HolidayBlockList")
private List<TimeBlock> holidayBlockList;
}
TimeBlock
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@XStreamAlias("TimeBlock")
public class TimeBlock {
private String dayOfWeek;
@XStreamAlias("TimeRange")
private TimeRange timeRange;
}
TimeRange
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@XStreamAlias("TimeRange")
public class TimeRange {
private String beginTime;
private String endTime;
}