XmlSerializer 将 C# 对象转换为 XML 字符串

8

我已经创建了一个C#类:

public class books {
    public int bookNum { get; set; }
    public class book {
        public string name { get; set; }
        public class record {
            public string borrowDate { get; set; }
            public string returnDate { get; set; }
        }
        public record[] records { get; set; }
    }
    public book[] books { get; set; }
}

但是当我使用 XmlSerializer 将其转换为 XML 字符串时,结果与以下 XML 不同。
我的 C# 类存在什么问题?我想使用 XmlSerializer 输出结果,而不是使用 XmlDocument。
有什么想法吗?提前感谢!
<books>
    <bookNum>2</bookNum>
    <book>
        <name>Book 1</name>
        <record>
            <borrowDate>2013-7-1</borrowDate>
            <returnDate>2013-7-12</returnDate>
        </record>
        <record>            
            <borrowDate>2013-8-1</borrowDate>
            <returnDate>2013-8-5</returnDate>
        </record>
    </book>
    <book>
        <name>Book 2</name>
        <record>
            <borrowDate>2013-6-1</borrowDate>
            <returnDate>2013-6-12</returnDate>
        </record>
        <record>            
            <borrowDate>2013-7-1</borrowDate>
            <returnDate>2013-7-5</returnDate>
        </record>
    </book>
</books>

编辑

以下是我的C#代码和输出结果:

books books = new books {
        bookNum = 2,
        Books = new books.book[] { 
            new books.book {  
                name = "Book1", 
                records = new books.book.record[] {
                    new books.book.record {
                        borrowDate = "2013-1-3",
                        returnDate = "2013-1-5"
                    },
                     new books.book.record {
                        borrowDate = "2013-2-3",
                        returnDate = "2013-4-5"
                    }
                }
            },
             new books.book {  
                name = "Book1", 
                records = new books.book.record[] {
                    new books.book.record {
                        borrowDate = "2013-1-3",
                        returnDate = "2013-1-5"
                    },
                     new books.book.record {
                        borrowDate = "2013-2-3",
                        returnDate = "2013-4-5"
                    }
                }
            }
        }
    };


    XmlSerializer xsSubmit = new XmlSerializer(typeof(books));

    XmlDocument doc = new XmlDocument();

    System.IO.StringWriter sww = new System.IO.StringWriter();
    XmlWriter writer = XmlWriter.Create(sww);
    xsSubmit.Serialize(writer, books);
    var xml = sww.ToString(); // Your xml
    context.Response.Write(xml);

XML:

<books>
    <bookNum>2</bookNum>
    <Books>
        <book>
            <name>Book1</name>
            <records>
                <record>
                    <borrowDate>2013-1-3</borrowDate>
                    <returnDate>2013-1-5</returnDate>
                </record>
                <record>
                    <borrowDate>2013-2-3</borrowDate>
                    <returnDate>2013-4-5</returnDate>
                </record>
            </records>
        </book>
        <book>
            <name>Book1</name>
            <records>
                <record>
                    <borrowDate>2013-1-3</borrowDate>
                    <returnDate>2013-1-5</returnDate>
                </record>
                <record>
                    <borrowDate>2013-2-3</borrowDate>
                    <returnDate>2013-4-5</returnDate>
                </record>
            </records>
         </book>
    </Books>
</books>

1
发布XMLserializer的输出? - David Colwell
结果与下面的xml不同 - 它是如何“不同”的?你得到了什么?(附注:您在XML中使用ISO8601 YYYY-MM-DD的非常规格式) - Alexei Levenkov
如果您进行XML序列化,则无法获取自定义的XML。 - Manish Parmar
实际上,Mac,你可以这样做。你需要将属性归属于更改它们的元素名称,但是你可以这样做。 - David Colwell
您可以通过实现 IXmlSerializable 属性来获得这样的输出 - 请参见我的回答中的第三个摘要。 - Andrii Kalytiiuk
4个回答

10
您无法使用标准序列化工具序列化您问题中的类,使得<book>节点与<bookNum>节点位于同一层级。
使用标准序列化工具保存类时,<book>节点的列表将始终嵌套在单独的数组节点中,该节点与<bookNum>节点位于同一层级。同样适用于book类中的records数组字段。
要生成您想要的XML输出 - 即<book>节点与<bookNum>节点在同一层级上 - 您必须在books类中实现IXmlSerializable接口进行自定义序列化。有关IXmlSerializable实现的示例,请访问以下链接:StackOverflow答案CodeProject文章
另一种解决方案是,根据我在回答中提到的用户Alexandr的评论,从List<book>类型继承您的books类,并在您的book类字段中具有从List<record>类型继承的类类型records
假设您已正确指定了XmlRoot、XmlElement、XmlArray和XmlArrayItem属性,则序列化您问题中的类。
[XmlRoot("books")]
public class books
{
    [XmlElement("bookNum")]
    public int bookNum { get; set; }

    [XmlRoot("book")]
    public class book
    {
        [XmlElement("name")]
        public string name { get; set; }

        [XmlRoot("record")]
        public class record
        {
            [XmlElement("borrowDate")]
            public string borrowDate { get; set; }

            [XmlElement("returnDate")]
            public string returnDate { get; set; }
        }

        [XmlArray("borrowRecords")]
        [XmlArrayItem("record")]
        public record[] records { get; set; }
    }

    [XmlArray("booksList")]
    [XmlArrayItem("book")]
    public book[] books { get; set; }
}

您将获得以下XML输出:

<books>
    <bookNum>2</bookNum>
    <booksList>
        <book>
            <name>Book 1</name>
            <borrowRecords>
                <record>
                    <borrowDate>2013-1-3</borrowDate>
                    <returnDate>2013-1-5</returnDate>
                </record>
                <record>            
                    <borrowDate>2013-2-3</borrowDate>
                    <returnDate>2013-4-5</returnDate>
                </record>
            </borrowRecords>
        </book>
        <book>
            <name>Book 2</name>
            <borrowRecords>
                <record>
                    <borrowDate>2013-1-3</borrowDate>
                    <returnDate>2013-1-5</returnDate>
                </record>
                <record>            
                    <borrowDate>2013-2-3</borrowDate>
                    <returnDate>2013-4-5</returnDate>
                </record>
            </borrowRecords>
        </book>
    </booksList>
</books>

2
你确定吗?因为我认为我之前通过让根类(在这种情况下是“books”)扩展“List<book>”,然后使用“XmlElement”属性对其应用属性来完成了这个。 - Alxandr
1
@Alxandr 继承列表是另一种选择 - 它改变了问题的条件。对于好主意加1。 - Andrii Kalytiiuk
我猜这也可以不用继承list来实现,只需提供一个 Add 方法即可,但我不知道 XmlSerializer 的内部工作原理... - Alxandr
@Alxandr 在快速使用谷歌搜索后,我没有找到使用Add方法的解决方案 - 我所能想到的仅是使用 IXmlSerializable 的不同变体。如果有替代方案的链接,将不胜感激。 - Andrii Kalytiiuk
1
这只是一个猜测,基于Add方法使得诸如var a = new List<string>() { "a", "b", "c" };的操作成为可能。此外,List<T>似乎没有实现IXmlSerializable接口,虽然我只知道它可能是一个序列化构造器。我想,既然.NET赋予Add方法特殊意义,那么XmlSerializer也可能会这样做... - Alxandr
我在 JustDecompile 中没有找到一个序列化构造函数。也许 List<T>IList<T>ICollection<T> 是由 XmlSerializer 显式处理的? - Alxandr

8
我对你的类代码做了以下更改。使用默认序列化程序无法复制XML序列化,因为它不会在没有容器元素的情况下复制“Record”元素。
[System.Xml.Serialization.XmlRoot("books")]
public class books 
{
    public int bookNum { get; set; }
    public class book {
        public string name { get; set; }
        public class record {
            public string borrowDate { get; set; }
            public string returnDate { get; set; }
        }
        public record[] records { get; set; }
    }
    public book[] books { get; set; }
}

将此序列化后,我得到以下输出。
<books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <bookNum>2</bookNum>
  <books>
    <book>
      <name>first</name>
      <records>
        <record>
          <borrowDate>19/07/2013 4:41:29 PM</borrowDate>
          <returnDate>19/07/2013 4:41:29 PM</returnDate>
        </record>
      </records>
    </book>
  </books>
</books>

使用这段测试代码

books bks = new books();
bks.bookNum = 2;
bks.books = new books.book[]{ new books.book{name="first", records = new books.book.record[] {new books.book.record{borrowDate = DateTime.Now.ToString(), returnDate = DateTime.Now.ToString()}}}};

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(books));

XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = new UnicodeEncoding(false, false); // no BOM in a .NET string
settings.Indent = true;
settings.OmitXmlDeclaration = true;

using(StringWriter textWriter = new StringWriter()) {
    using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
        serializer.Serialize(xmlWriter, bks);
    }
    return textWriter.ToString(); //This is the output as a string
}

我们不能让<bookNum>和<book>处于同一级别吗? - Ricky Yip
3
"books"是"book"的容器元素。这就是它在重新序列化时知道它们属于同一属性的方式。 - David Colwell
C#的编码标准很糟糕。你可以看出作者是一位Java开发者。 - csharpforevermore

5
我知道现在可能有点晚了,但我想说明的是,我只使用XmlElementAttribute就实现了您所需的结构。

我通过使用XSD.exe从xml生成模式定义,并从xsd文件生成.Net代码来发现了这一点。据我所知,这适用于.Net 3.5至4.6。

以下是我使用的类定义:

public class books
{
    public int bookNum { get; set; }
    public class book {
        public string name { get; set; }
        public class record {
            public string borrowDate { get; set; }
            public string returnDate { get; set; }
        }
        [XmlElement("record")]
        public record[] records { get; set; }
    }
    [XmlElement("book")]
    public book[] allBooks { get; set; }
}

这里有一个LinqPad代码片段,用于说明序列化/反序列化(基于David Colwell的代码片段,顺便感谢他关于如何排除BOM的提示,正是我需要的):

books bks = new books();
books bks2 = null;
bks.bookNum = 2;
bks.allBooks = new books.book[] 
        { 
            new books.book {
                name="book 1", 
                records = new books.book.record[] {
                        new books.book.record{borrowDate = DateTime.Now.ToString(), returnDate = DateTime.Now.ToString()}
                    }
                },
            new books.book { 
                name="book 2", 
                records = new books.book.record[] { 
                        new books.book.record{borrowDate = DateTime.Now.ToString(), returnDate = DateTime.Now.ToString()}, 
                        new books.book.record{borrowDate = DateTime.Now.ToString(), returnDate = DateTime.Now.ToString()}}
                    },
        };
string xmlString;

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(books));

XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = new UnicodeEncoding(false, false); // no BOM in a .NET string
settings.Indent = true;
settings.OmitXmlDeclaration = true;

XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
// exclude xsi and xsd namespaces by adding the following:
ns.Add(string.Empty, string.Empty);

using(StringWriter textWriter = new StringWriter()) {
    using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
        serializer.Serialize(xmlWriter, bks, ns);
    }
    xmlString = textWriter.ToString(); //This is the output as a string
}

xmlString.Dump();

// Deserialize the xml string now       
using ( TextReader reader = new StringReader(xmlString) ) {
    bks2 = ( books )serializer.Deserialize(reader);
}

bks2.Dump();

这会生成XML,可以在不实现IXmlSerializable的情况下进行序列化和反序列化,例如:
<books>
  <bookNum>2</bookNum>
  <book>
    <name>book 1</name>
    <record>
      <borrowDate>2/2/2016 5:57:25 PM</borrowDate>
      <returnDate>2/2/2016 5:57:25 PM</returnDate>
    </record>
  </book>
  <book>
    <name>book 2</name>
    <record>
      <borrowDate>2/2/2016 5:57:25 PM</borrowDate>
      <returnDate>2/2/2016 5:57:25 PM</returnDate>
    </record>
    <record>
      <borrowDate>2/2/2016 5:57:25 PM</borrowDate>
      <returnDate>2/2/2016 5:57:25 PM</returnDate>
    </record>
  </book>
</books>

支持使用xsd.exe从所需的XML创建类的想法!一定会把它加入我的工具包。 - Andrew Steitz
你可以找到很好的在线替代品。 - csharpforevermore

0

如果你需要其他类,例如在books类中的book2,你需要一些特殊的指令来实现它。 例子

public class books
{     
   public int bookNum {get; set; }
   public class book {
         public string name {get; set; }
         public class record {
             public string borrowDate {get; set; }
             public string returnDate {get; set; }
         }
         [XmlElement ("record")]
         public record [] records {get; set; }
     }
     [XmlElement ("book")]
     public book [] allBooks {get; set; }

     public int book2Num {get; set; }
     public class book2 {
         public string name {get; set; }
         public class record {
             public string borrowDate {get; set; }
             public string returnDate {get; set; }
         }
         [XmlElement ("record")]
         public record [] records {get; set; }
     }
     [XmlElement ("book2")]
     public book2 [] allBook2 {get; set; }
}`

当我尝试运行程序时,出现了以下错误:

"附加信息:反射类型时出错 "


请编辑答案以详细阐述特殊指令的主题。 - sorak

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