XStream null值序列化時不會顯示標(biāo)簽

一躏哩、問題

我在使用XStream時涝婉,遇到一個很坑的問題扮休,一旦屬性值為null,這個屬性對應(yīng)的標(biāo)簽就不會輸出,例如:

@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));
}

輸出結(jié)果:

<Student>
  <Age>1</Age>
</Student>

咱們想要的結(jié)果卻是如下:

<Student>
  <Age>1</Age>
  <Name></Name>
</Student>

二厢拭、解決方案

我找了很多方案兰英,都是自己寫一個Converter,但是這些自己寫的converter往往出現(xiàn)很多不可預(yù)知的BUG供鸠。最終我的解決方案參考這個鏈接:https://stackoverflow.com/questions/13066360/xstream-serialize-null-values雖然還是自己寫一個Converter,但是和XStream處理邏輯一樣畦贸,并且可以輸出null值對應(yīng)的標(biāo)簽

解決思路:新建一個NullConverter繼承ReflectionConverter這個類,重寫doMarshal方法楞捂,先把原來ReflectionConverter里面的代碼全部copy下來薄坏。原來的處理邏輯是,if字段為null寨闹,就不進(jìn)行處理了颤殴,咱們可以加一個else,將空值的標(biāo)簽也輸出鼻忠。
以下是修改的內(nèi)容(可以在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值的標(biāo)簽也輸出,最后的參數(shù)就是標(biāo)簽里的值,咱們這里輸出空字符串
        writeField(info.fieldName,null,info.type,info.definedIn,"");
    }
}

通過注冊這個NullConverter(需要將這個轉(zhuǎn)換器的優(yōu)先級調(diào)到最低)咱們就可以輸出Null值對應(yīng)的標(biāo)簽啦:

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());
    // 將轉(zhuǎn)換器注冊到非常低的位置非常重要
    xStream.registerConverter(nullConverter,XStream.PRIORITY_VERY_LOW);
    System.out.println(xStream.toXML(stu));
}

輸出結(jié)果

<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的標(biāo)簽
 * 注意在使用的時候,記得將轉(zhuǎn)換器注冊到非常低的位置: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值的標(biāo)簽也輸出
                        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;
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帖蔓,一起剝皮案震驚了整個濱河市矮瘟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塑娇,老刑警劉巖澈侠,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異埋酬,居然都是意外死亡哨啃,警方通過查閱死者的電腦和手機(jī)烧栋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拳球,“玉大人审姓,你說我怎么就攤上這事∽>” “怎么了魔吐?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長莱找。 經(jīng)常有香客問我酬姆,道長,這世上最難降的妖魔是什么奥溺? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任辞色,我火速辦了婚禮,結(jié)果婚禮上浮定,老公的妹妹穿的比我還像新娘相满。我一直安慰自己,他們只是感情好壶唤,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布雳灵。 她就那樣靜靜地躺著棕所,像睡著了一般闸盔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琳省,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天迎吵,我揣著相機(jī)與錄音,去河邊找鬼针贬。 笑死击费,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桦他。 我是一名探鬼主播蔫巩,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼快压!你這毒婦竟也來了圆仔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蔫劣,失蹤者是張志新(化名)和其女友劉穎坪郭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脉幢,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡歪沃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年嗦锐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沪曙。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奕污,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出珊蟀,到底是詐尸還是另有隱情菊值,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布育灸,位于F島的核電站腻窒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏磅崭。R本人自食惡果不足惜儿子,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砸喻。 院中可真熱鬧柔逼,春花似錦、人聲如沸割岛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽癣漆。三九已至维咸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惠爽,已是汗流浹背癌蓖。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留婚肆,地道東北人租副。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像较性,于是被迫代替她去往敵國和親用僧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容