如何在XML序列化时隐藏可为null的的字段

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在XML序列化时隐藏可为null的的字段相关的知识,希望对你有一定的参考价值。

使用XmlSerializer可以方便的将对象序列化为xml,实现应用之间的数据交互。但是XmlSerializer却不能很好地序列化类型中的可为null的字段。
例如,有如下定义的类Person:
[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

public string FirstName get; set;

public string LastName get; set;

public int? Age get; set;

[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

public string FirstName get; set;

public string LastName get; set;

public int? Age get; set;


其中的Age属性为Nullable int类型。
我们的实例化代码如下所示:
var person = new Person

FirstName = "First",
;
person.OutputXml(Console.Out);
var person = new Person

FirstName = "First",
;
person.OutputXml(Console.Out);

其中方法OutputXml为扩展方法,使用XmlSerializer来序列化对象,具体定义为:
public static void OutputXml<T>(this T dto, TextWriter textWriter)

var xmlTypeMapping = typeof(T);
var serializer = new XmlSerializer(xmlTypeMapping);
var xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
using (var writer = new XmlTextWriter(textWriter) Formatting = Formatting.Indented )

serializer.Serialize(writer, dto, xmlns);


public static void OutputXml<T>(this T dto, TextWriter textWriter)

var xmlTypeMapping = typeof(T);
var serializer = new XmlSerializer(xmlTypeMapping);
var xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
using (var writer = new XmlTextWriter(textWriter) Formatting = Formatting.Indented )

serializer.Serialize(writer, dto, xmlns);



使用上述方法序列化对象person,得到的结果为:

注意到虽然Age属性为空,却仍然被序列化成 了古怪的xml,这往往不是所期望的结果。事实上同为空的LastName属性的序列化正是大多数情况下我们所期望的行为——忽略为空的属性。
另一方面,如果试图序列化类属性为xml 属性(而非xml元素),则甚至不能工作。例如如果我们将Person类的定义修改成如下形式以便序列化为xml属性:
[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

[XmlAttribute]
public string FirstName get; set;

[XmlAttribute]
public string LastName get; set;

[XmlAttribute]
public int? Age get; set;

[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

[XmlAttribute]
public string FirstName get; set;

[XmlAttribute]
public string LastName get; set;

[XmlAttribute]
public int? Age get; set;


Xmlserializer甚至无法正常序列化上面同样的person对象并抛出如下“XmlAttribute/XmlText cannot be used to encode complex types”的错误:

如何解决上述的2个问题呢?
1. 序列化可为null属性输出为XmlElement
为了在序列化可空属性的时候忽略空值的古怪输出,我们可以在Person类中定义一个返回bool的特殊方法 ShouldSerializeAge,并在其中实现定义我们的序列化规则:
[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

public string FirstName get; set;

public string LastName get; set;

public int? Age get; set;

public bool ShouldSerializeAge()

return Age.HasValue;


[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

public string FirstName get; set;

public string LastName get; set;

public int? Age get; set;

public bool ShouldSerializeAge()

return Age.HasValue;



在这里我们定义序列化规则为:序列化非空Age。注意该方法的名字一定是以ShouldSerialize开头并连接上需要自定义规则的属性名 称。这样序列化出来的person对象为:

空的Age和空的LastName一样在序列化时被忽略了输出。正是我们期望的结果!
2. 序列化可为null的属性输出为XmlAttribute
由于可空属性无法被直接序列化为XmlAttribute,我们需要采用间接的办法——定义间接属性。此时我们可以如下定义Person类:
[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

[XmlAttribute]
public string FirstName get; set;

[XmlAttribute]
public string LastName get; set;

[XmlIgnore] // (4)
public int? Age get; set;

[XmlAttribute(AttributeName = "Age")] // (1)
public string AgeValue

get

// (2)
return Age.HasValue ? Age.Value.ToString() : null;

set

int result;
// (3)
Age = int.TryParse(value, out result) ? result : (int?) null;



[Serializable]
[XmlRoot(ElementName = "Person")]
public class Person

[XmlAttribute]
public string FirstName get; set;

[XmlAttribute]
public string LastName get; set;

[XmlIgnore] // (4)
public int? Age get; set;

[XmlAttribute(AttributeName = "Age")] // (1)
public string AgeValue

get

// (2)
return Age.HasValue ? Age.Value.ToString() : null;

set

int result;
// (3)
Age = int.TryParse(value, out result) ? result : (int?) null;


参考技术A webservice这个好像不直接支持json的,一个比较好的替代方法是在C#端先把返回值序列化成json字符串在返回了, 但是这可能会限制程序的易用性和伸缩性,不过要是仅仅简单的返回数据的话,应该没有任何问题,这个可能需要你定义一些简单的协议

如何在 Protobuf-Net 中保留一个可为空值的数组?

【中文标题】如何在 Protobuf-Net 中保留一个可为空值的数组?【英文标题】:How can I persist an array of a nullable value in Protobuf-Net? 【发布时间】:2013-10-01 09:06:27 【问题描述】:

我正在从 BinaryFormatter 迁移到 Protobuf-net(到目前为止,它似乎在存储大小和反序列化时间方面都提供了巨大的改进)。

然而,我遇到的一个问题是 double?[] 数组不会以它们被序列化的相同形式反序列化。数组中任何为 null 的值都会被完全删除 - 即,如果我从一个包含 [null, null, 1, 2, 3, null] 的 6 个元素的数组开始,在反序列化之后我会得到一个 [1 , 2, 3]。对于我的程序,我必须以与序列化之前完全相同的形式检索这些数组 - 如果使用 BinaryFormatter 就会发生这种情况。

到目前为止,我提出的一个解决方案是为每个数组创建两个数组,一个是 double[],每个元素都有一个值,另一个是 bool[],它可以用来描述原始值是否为null - 但是由于各种原因,这非常低效。

我可以看到在之前的相关问题中提到,ProtoMember 可能有一个“SupportNull”选项,但是我找不到任何文档清楚地显示如何实现这一点,并且无法自己解决这个问题.

任何人都可以提供任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:
using ProtoBuf;
using ProtoBuf.Meta;
using System;
[ProtoContract]
class Foo

    [ProtoMember(1)]
    public double?[] Values  get; set; 

static class Program

    static void Main()
    
        // configure the model; SupportNull is not currently available
        // on the attributes, so need to tweak the model a little
        RuntimeTypeModel.Default.Add(typeof(Foo), true)[1].SupportNull = true;

        // invent some data, then clone it (serialize+deserialize)
        var obj = new Foo  Values = new double?[] 1,null, 2.5, null, 3;
        var clone = Serializer.DeepClone(obj);

        // check we got all the values back
        foreach (var value in clone.Values)
        
            Console.WriteLine(value);
        
    

【讨论】:

感谢 Marc 提供的优秀样品。但是最好有一个属性。实际上,在静态构造函数中设置它并不总是在反序列化时调用。然后我们必须将“SupportNull”设置在它应该在的类之外的其他地方。但这样做违背了良好编程的主要原则:高内聚和低耦合。【参考方案2】:

它实际上不支持开箱即用,您必须操纵RuntimeTypeModel 来明确设置它应该允许空值。

RuntimeTypeModel.Default[typeof(YourObjectType)][(tag)].SupportNull = true;

例子:

var nullable = new ObjectWithNullables()  IntArray = new int?[]  null, 1, 2, null  ;

// returns 2 elements out of 4
//var resultA = Deserialize<ObjectWithNullables>(Serialize<ObjectWithNullables>(nullable));

RuntimeTypeModel.Default[typeof(ObjectWithNullables)][1].SupportNull = true;

// returns 4 elements out of 4
var resultA = Deserialize<ObjectWithNullables>(Serialize<ObjectWithNullables>(nullable));


    [ProtoContract]
    public class ObjectWithNullables
    
        [ProtoMember(1)]
        public int?[] IntArray  get; set; 
    

【讨论】:

@user2262704 添加了我的意思的示例 其实是支持的。 @MarcGravell 刚刚找到了相同的解决方案 ^^ 更新了我的答案

以上是关于如何在XML序列化时隐藏可为null的的字段的主要内容,如果未能解决你的问题,请参考以下文章

序列化一个可为空的 int

使用 XmlSerializer 将空 xml 属性值反序列化为可为空的 int 属性

如何从XML *向SQL Server DATE字段*中插入NULL

Jackson忽略空字段

如果它为空或为空,如何从序列化中忽略可为空的属性?

如何在 SQL Server 中使用 XML 节点传递 NULL 而不是字符串格式的“NULL”值