将对象序列化为字符串。

361

我有以下方法将一个对象保存到文件:

// Save an object out to the disk
public static void SerializeObject<T>(this T toSerialize, String filename)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
    TextWriter textWriter = new StreamWriter(filename);

    xmlSerializer.Serialize(textWriter, toSerialize);
    textWriter.Close();
}

我承认我没有编写它(我只将其转换为一个带有类型参数的扩展方法)。

现在,我需要将xml作为字符串返回给我(而不是保存到文件中)。我正在研究这个问题,但我还没有想出来。

我认为对于熟悉这些对象的人来说,这可能非常容易。如果不是,我最终会弄清楚的。

11个回答

617

使用 StringWriter 代替 StreamWriter

public static string SerializeObject<T>(this T toSerialize)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

    using(StringWriter textWriter = new StringWriter())
    {
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}

请注意,在XmlSerializer构造函数中,重要的是使用toSerialize.GetType()而不是typeof(T):如果您使用第一个,代码将涵盖所有可能的T子类(对于该方法有效),而使用后者将在传递从T派生的类型时失败。
这里有一个链接,其中包含一些示例代码,可以证明这种说法。当使用typeof(T)时,XmlSerializer会抛出异常,因为您向调用SerializeObject的方法传递了派生类型的实例,而该方法在派生类型的基类中定义:http://ideone.com/1Z5J1
此外,Ideone使用Mono来执行代码;使用Microsoft .NET运行时获得的实际Exception与Ideone上显示的Message不同,但结果相同。

2
@JohnSaunders:好的,把这个讨论移到 Meta 是个好主意。这是我刚在 Meta Stack Overflow 上发布的问题链接:点击这里 - Fulvio
37
@casperOne 各位,请别再对我的回答进行干扰。关键是要使用 StringWriter 而不是 StreamWriter,其它内容与问题无关。如果你想讨论诸如 typeof(T)toSerialize.GetType() 等详细信息,请另开话题,但不要在我的回答中讨论。谢谢。 - dtb
9
抱歉,但Stack Overflow是协作编辑的。此外,这个特定的答案已经在Meta上讨论过,因此该编辑保持不变。如果您不同意,请在Meta上回复该帖子,说明为什么您认为您的答案是一个特例,不应该被协作编辑。 - casperOne
2
从编码角度来看,这是我见过的最短的例子。+1 - froggythefrog
15
StringWriter实现了IDisposable接口,因此应该放在using块中。 - TrueWill
显示剩余6条评论

92

序列化和反序列化XML/JSON(SerializationHelper.cs)(您可以使用Newtonsoft.Json Nuget包进行JSON序列化):

using Newtonsoft.Json;
using System.IO;
using System.Xml.Serialization;

namespace MyProject.Helpers
{
    public static class SerializationHelper
    {
        public static T DeserializeXml<T>(this string toDeserialize)
        {
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            using (StringReader textReader = new StringReader(toDeserialize))
            {
                return (T)xmlSerializer.Deserialize(textReader);
            }
        }

        public static string SerializeXml<T>(this T toSerialize)
        {
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            using (StringWriter textWriter = new StringWriter())
            {
                xmlSerializer.Serialize(textWriter, toSerialize);
                return textWriter.ToString();
            }
        }

        public static T DeserializeJson<T>(this string toDeserialize)
        {
            return JsonConvert.DeserializeObject<T>(toDeserialize);
        }

        public static string SerializeJson<T>(this T toSerialize)
        {
            return JsonConvert.SerializeObject(toSerialize);
        }
    }
}

或者,您可以使用System.Text.Json Nuget包来进行Json序列化。
using System.Text.Json;

...

public static T DeserializeJson<T>(this string toDeserialize, JsonSerializerOptions options = null)
{
    return JsonSerializer.Deserialize<T>(toDeserialize, options);
}

public static string SerializeJson<T>(this T toSerialize, JsonSerializerOptions options = null)
{
    return JsonSerializer.Serialize<T>(toSerialize, options);
}

19
感谢你还展示了如何反序列化,与其他答案不同。+1 - deadlydog
6
不过需要进行一个小改动,即在DeserializeObject函数中返回T类型而非object类型,并将返回的object对象转换为T类型。这样就会返回强类型对象而非通用对象。 - deadlydog
感谢@deadlydog,我已经修复了。 - ADM-IT
3
TextWriter有一个Dispose()函数需要调用。所以你忘记使用Using语句了。 - Herman Van Der Blom
我真的很喜欢这个实现,只是想添加一些东西和可能清理代码的事情: 首先:使用Newtonsoft.Json的NuGet依赖是Newtonsoft.Json更加容错:public static T? DeserializeXml(this string toDeserialize) { var xmlSerializer = new XmlSerializer(typeof(T)); using var sr = new StringReader(toDeserialize); try { return (T?)xmlSerializer.Deserialize(sr); } catch { return default(T); } } - Netzmensch
@Netzmensch 如果你遇到错误,那意味着出了问题,你需要修复它,而不是隐藏错误。绝对不能隐藏错误。<绝对不能>。(编程经验25年)。 - ADM-IT

76

我知道这不是问题的真正答案,但基于问题的投票数和被接受的答案,我怀疑人们实际上正在使用代码将对象序列化为字符串。

使用XML序列化会向输出中添加不必要的额外文本垃圾。

对于以下类:

public class UserData
{
    public int UserId { get; set; }
}

它生成

<?xml version="1.0" encoding="utf-16"?>
<UserData xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <UserId>0</UserId>
</UserData>

更好的解决方案是使用JSON序列化(其中最好的之一是Json.NET)。 要序列化一个对象:

var userData = new UserData {UserId = 0};
var userDataString = JsonConvert.SerializeObject(userData);

反序列化对象:

var userData = JsonConvert.DeserializeObject<UserData>(userDataString);

序列化后的 JSON 字符串将如下所示:

{"UserId":0}

6
在这种情况下,你是正确的,但是你看过大型的XML文档和JSON文档吗?JSON文档很难阅读。你所说的“垃圾”,例如命名空间可以被压缩。生成的XML可以像JSON一样清晰,但总是比JSON更易读。阅读性是XML比JSON更大的优势。 - Herman Van Der Blom
2
如果你在网上搜索“json在线解析器”,你会发现一些在线的json解析器,它们可以以更易读的方式格式化json字符串。 - xhafan
OP 需要 XML 而不是 JSON。这与主题无关。 - Jason P Sallinger
@Herman Van Der Blom。Json比xml更易读。xml太过于视觉混乱。xml唯一的优点可能是xpath,但是对命名空间的处理(或者说缺乏处理)是彻底失败的。 - undefined

38

代码安全注意事项

关于被接受的答案,在XmlSerializer构造函数中使用toSerialize.GetType()而不是typeof(T)非常重要:如果你使用第一个,代码将涵盖所有可能的情况,而使用后者有时会失败。

这里有一个链接,其中包含一些示例代码,可以证明这个说法,当使用typeof(T)时,XmlSerializer会抛出异常,因为你将派生类型的实例传递给调用SerializeObject<T>()的方法,该方法在派生类型的基类中定义:http://ideone.com/1Z5J1请注意,Ideone使用Mono来执行代码:你在Microsoft .NET运行时中得到的实际异常消息与Ideone上显示的不同,但它仍然会失败。

为了完整起见,在此发布完整的代码示例供将来参考,以防Ideone(我发布代码的地方)将来不可用:

using System;
using System.Xml.Serialization;
using System.IO;

public class Test
{
    public static void Main()
    {
        Sub subInstance = new Sub();
        Console.WriteLine(subInstance.TestMethod());
    }

    public class Super
    {
        public string TestMethod() {
            return this.SerializeObject();
        }
    }

    public class Sub : Super
    {
    }
}

public static class TestExt {
    public static string SerializeObject<T>(this T toSerialize)
    {
        Console.WriteLine(typeof(T).Name);             // PRINTS: "Super", the base/superclass -- Expected output is "Sub" instead
        Console.WriteLine(toSerialize.GetType().Name); // PRINTS: "Sub", the derived/subclass

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        StringWriter textWriter = new StringWriter();

        // And now...this will throw and Exception!
        // Changing new XmlSerializer(typeof(T)) to new XmlSerializer(subInstance.GetType()); 
        // solves the problem
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}

12
为了正确关闭/处理对象,您还应该使用using (StringWriter textWriter = new StringWriter() {}) - Amicable
我完全同意你的看法,@Amicable!我只是尽可能地保持我的代码示例与 OP 的代码示例相似,以突出我的观点,即关于对象类型的问题。无论如何,我们需要记住,using 语句既是我们的好朋友,也是我们亲爱的实现 IDisposable 接口的对象的好朋友 ;) - Fulvio
扩展方法使您能够“添加”方法到现有类型,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 - Adrian

13

我的看法是...

        string Serialise<T>(T serialisableObject)
        {
            var xmlSerializer = new XmlSerializer(serialisableObject.GetType());

            using (var ms = new MemoryStream())
            {
                using (var xw = XmlWriter.Create(ms, 
                    new XmlWriterSettings()
                        {
                            Encoding = new UTF8Encoding(false),
                            Indent = true,
                            NewLineOnAttributes = true,
                        }))
                {
                    xmlSerializer.Serialize(xw,serialisableObject);
                    return Encoding.UTF8.GetString(ms.ToArray());
                }
            }
        }

2
+1 是使用 XmlWriterSettings()。我希望我的序列化 XML 不会浪费空间,设置 Indent = false 和 NewLineOnAttributes = false 就可以了。 - Lee Richardson
谢谢@LeeRichardson - 我需要做完全相反的事情,同时在.net下XmlWriter默认为UTF16,这也不是我要写出的。 - oPless
使用MemoryStream和Encoding GetString的组合,将包括Preamble/BOM作为字符串中的第一个字符。 另请参阅https://dev59.com/e2gt5IYBdhLWcg3w8h01 - Jamee
@Jamee,“Encoding = UTF8Encoding(false)”表示不要写入BOM,参见https://learn.microsoft.com/en-us/dotnet/api/system.text.utf8encoding.-ctor?view=netframework-4.8 ...自.NET4以来,这种行为是否发生了变化? - oPless
@oPless,你说得对,我误解了我提供的链接的答案。 - Jamee

5
public static string SerializeObject<T>(T objectToSerialize)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            MemoryStream memStr = new MemoryStream();

            try
            {
                bf.Serialize(memStr, objectToSerialize);
                memStr.Position = 0;

                return Convert.ToBase64String(memStr.ToArray());
            }
            finally
            {
                memStr.Close();
            }
        }

        public static T DerializeObject<T>(string objectToDerialize)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            byte[] byteArray = Convert.FromBase64String(objectToDerialize);
            MemoryStream memStr = new MemoryStream(byteArray);

            try
            {
                return (T)bf.Deserialize(memStr);
            }
            finally
            {
                memStr.Close();
            }
        }

你可以使用using (MemoryStream memStr = new MemoryStream())来替代try/catch语句。 - undefined

3

我觉得有必要分享这个被篡改的代码,因为我没有声望,所以无法进行评论。

using System;
using System.Xml.Serialization;
using System.IO;

namespace ObjectSerialization
{
    public static class ObjectSerialization
    {
        // THIS: (C): https://dev59.com/jXE95IYBdhLWcg3wJKl_
        /// <summary>
        /// A helper to serialize an object to a string containing XML data of the object.
        /// </summary>
        /// <typeparam name="T">An object to serialize to a XML data string.</typeparam>
        /// <param name="toSerialize">A helper method for any type of object to be serialized to a XML data string.</param>
        /// <returns>A string containing XML data of the object.</returns>
        public static string SerializeObject<T>(this T toSerialize)
        {
            // create an instance of a XmlSerializer class with the typeof(T)..
            XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

            // using is necessary with classes which implement the IDisposable interface..
            using (StringWriter stringWriter = new StringWriter())
            {
                // serialize a class to a StringWriter class instance..
                xmlSerializer.Serialize(stringWriter, toSerialize); // a base class of the StringWriter instance is TextWriter..
                return stringWriter.ToString(); // return the value..
            }
        }

        // THIS: (C): VPKSoft, 2018, https://www.vpksoft.net
        /// <summary>
        /// Deserializes an object which is saved to an XML data string. If the object has no instance a new object will be constructed if possible.
        /// <note type="note">An exception will occur if a null reference is called an no valid constructor of the class is available.</note>
        /// </summary>
        /// <typeparam name="T">An object to deserialize from a XML data string.</typeparam>
        /// <param name="toDeserialize">An object of which XML data to deserialize. If the object is null a a default constructor is called.</param>
        /// <param name="xmlData">A string containing a serialized XML data do deserialize.</param>
        /// <returns>An object which is deserialized from the XML data string.</returns>
        public static T DeserializeObject<T>(this T toDeserialize, string xmlData)
        {
            // if a null instance of an object called this try to create a "default" instance for it with typeof(T),
            // this will throw an exception no useful constructor is found..
            object voidInstance = toDeserialize == null ? Activator.CreateInstance(typeof(T)) : toDeserialize;

            // create an instance of a XmlSerializer class with the typeof(T)..
            XmlSerializer xmlSerializer = new XmlSerializer(voidInstance.GetType());

            // construct a StringReader class instance of the given xmlData parameter to be deserialized by the XmlSerializer class instance..
            using (StringReader stringReader = new StringReader(xmlData))
            {
                // return the "new" object deserialized via the XmlSerializer class instance..
                return (T)xmlSerializer.Deserialize(stringReader);
            }
        }

        // THIS: (C): VPKSoft, 2018, https://www.vpksoft.net
        /// <summary>
        /// Deserializes an object which is saved to an XML data string.
        /// </summary>
        /// <param name="toDeserialize">A type of an object of which XML data to deserialize.</param>
        /// <param name="xmlData">A string containing a serialized XML data do deserialize.</param>
        /// <returns>An object which is deserialized from the XML data string.</returns>
        public static object DeserializeObject(Type toDeserialize, string xmlData)
        {
            // create an instance of a XmlSerializer class with the given type toDeserialize..
            XmlSerializer xmlSerializer = new XmlSerializer(toDeserialize);

            // construct a StringReader class instance of the given xmlData parameter to be deserialized by the XmlSerializer class instance..
            using (StringReader stringReader = new StringReader(xmlData))
            {
                // return the "new" object deserialized via the XmlSerializer class instance..
                return xmlSerializer.Deserialize(stringReader);
            }
        }
    }
}


我知道这已经有些过时了,但由于您提供了一个非常好的答案,所以我会添加一些小的评论,就像我在代码审核PR上一样:使用泛型时应该对T设置约束。这有助于保持整洁,而不是将代码库和框架中引用的每个对象都序列化。 - Frank R. Haugen

1

我无法使用xhafan建议的JSONConvert方法。

即使在添加了“System.Web.Extensions”程序集引用后,我仍然无法访问JSONConvert。

但是,一旦您添加了该引用,您可以使用以下方式获得相同的字符串输出:

JavaScriptSerializer js = new JavaScriptSerializer();
string jsonstring = js.Serialize(yourClassObject);

2
JSONConvert类位于NewtonSoft.Json命名空间中。请转到VS的包管理器,然后下载NewtonSoft.Json包。 - Amir Shrestha

0

[VB]

Public Function XmlSerializeObject(ByVal obj As Object) As String

    Dim xmlStr As String = String.Empty

    Dim settings As New XmlWriterSettings()
    settings.Indent = False
    settings.OmitXmlDeclaration = True
    settings.NewLineChars = String.Empty
    settings.NewLineHandling = NewLineHandling.None

    Using stringWriter As New StringWriter()
        Using xmlWriter__1 As XmlWriter = XmlWriter.Create(stringWriter, settings)

            Dim serializer As New XmlSerializer(obj.[GetType]())
            serializer.Serialize(xmlWriter__1, obj)

            xmlStr = stringWriter.ToString()
            xmlWriter__1.Close()
        End Using

        stringWriter.Close()
    End Using

    Return xmlStr.ToString
End Function

Public Function XmlDeserializeObject(ByVal data As [String], ByVal objType As Type) As Object

    Dim xmlSer As New System.Xml.Serialization.XmlSerializer(objType)
    Dim reader As TextReader = New StringReader(data)

    Dim obj As New Object
    obj = DirectCast(xmlSer.Deserialize(reader), Object)
    Return obj
End Function

[C#]

public string XmlSerializeObject(object obj)
{
    string xmlStr = String.Empty;
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = false;
    settings.OmitXmlDeclaration = true;
    settings.NewLineChars = String.Empty;
    settings.NewLineHandling = NewLineHandling.None;

    using (StringWriter stringWriter = new StringWriter())
    {
        using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
        {
            XmlSerializer serializer = new XmlSerializer( obj.GetType());
            serializer.Serialize(xmlWriter, obj);
            xmlStr = stringWriter.ToString();
            xmlWriter.Close();
        }
    }
    return xmlStr.ToString(); 
}

public object XmlDeserializeObject(string data, Type objType)
{
    XmlSerializer xmlSer = new XmlSerializer(objType);
    StringReader reader = new StringReader(data);

    object obj = new object();
    obj = (object)(xmlSer.Deserialize(reader));
    return obj;
}

0
通用的XML、JSON和二进制序列化/反序列化工具,可将数据转换为字符串形式。
public static class SerializationHelper
{

    public static string Serialize<T>(this T toSerialize, OutTypeEnum oType)
    {
        if(oType == OutTypeEnum.JSON)
            return SerializeJson<T>(toSerialize);
        else if(oType == OutTypeEnum.XML)
            return SerializeXml<T>(toSerialize);
        else
            return SerializeBin<T>(toSerialize);
    }

    public static T Deserialize<T>(this string toDeserialize, OutTypeEnum oType)
    {
        if (oType == OutTypeEnum.JSON)
            return DeserializeJson<T>(toDeserialize);
        else if (oType == OutTypeEnum.XML)
            return DeserializeXml<T>(toDeserialize);
        else
            return DeserializeBin<T>(toDeserialize);
    }

    public static string SerializeXml<T>(this T toSerialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        using (StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static T DeserializeXml<T>(this string toDeserialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        using (StringReader textReader = new StringReader(toDeserialize))
        {
            return (T)xmlSerializer.Deserialize(textReader);
        }
    }

    public static string SerializeJson<T>(this T toSerialize) => JsonSerializer.Serialize(toSerialize);

    public static T DeserializeJson<T>(this string toDeserialize) => (T)JsonSerializer.Deserialize(toDeserialize, typeof(T));

    public static string SerializeBin<T>(this T toSerialize)
    {
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        MemoryStream memStr = new MemoryStream();

        try
        {
            bf.Serialize(memStr, toSerialize);
            memStr.Position = 0;

            return Convert.ToBase64String(memStr.ToArray());
        }
        finally
        {
            memStr.Close();
        }
    }

    public static T DeserializeBin<T>(this string objectToDerialize)
    {
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        byte[] byteArray = Convert.FromBase64String(objectToDerialize);
        MemoryStream memStr = new MemoryStream(byteArray);

        try
        {
            return (T)bf.Deserialize(memStr);
        }
        finally
        {
            memStr.Close();
        }
    }
}

public enum OutTypeEnum
{
    JSON,
    XML,
    BIN
}

用法:

SomeClass obj = new SomeClass();
string serializedObject = obj.Serialize(OutTypeEnum.JSON);

SomeClass newObj = serializedObject.Deserialize<SomeClass>(OutTypeEnum.JSON);

请记住,Stack Overflow 不仅旨在解决当前的问题,还旨在帮助未来的读者找到类似问题的解决方案,这需要理解底层代码。这对于我们社区中的初学者尤其重要,因为他们可能不熟悉语法。鉴于此,请编辑您的回答以包括您正在做什么以及为什么您认为这是最佳方法的解释。 - Jeremy Caney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接