【问题标题】:Unable to Deserialize XML in C# - Unrecgnized element 'add'无法在 C# 中反序列化 XML - 无法识别的元素“添加”
【发布时间】:2018-07-17 14:39:34
【问题描述】:

我有一个使用自定义部分进行配置的 C# 应用程序。我将 XML 的那部分定义为字符串。字符串如下所示:

var xml = @"<departments>
  <department id=""1"" name=""Sporting Goods"">
    <products>
      <product name=""Basketball"" price=""9.99"">
        <add key=""Color"" value=""Orange"" />
        <add key=""Brand"" value=""[BrandName]"" />
      </product>
    </products>
  </department>
</departments>"; 

此 XML 与我描述的类 here 定义的架构相匹配。当我将上面的字符串传递给Departments.Deserialize 方法时,我收到一个错误。错误说:“无法识别的元素'添加'”。调试器跳转到我的Departments 类中的这一行。

public void ReadXml(XmlReader reader)
{
  this.DeserializeElement(reader, false);
}

我认为错误是指“产品”元素中的“添加”元素。但是,ProductConfigurationElement 有一个名为 KeyValueConfigurationCollection Items 的属性。出于这个原因,add 似乎可以工作。

为什么我会收到这个错误我该如何修复我的代码以便可以反序列化上面显示的 XML 字符串?

【问题讨论】:

  • 你能告诉我们部门和产品代码吗?
  • @L0uis - 代码在我上面引用的相关 SO 帖子中,即here
  • 所有属性都需要两个双引号而不是一个。您有一个字符串,并且属性周围的双引号会产生错误。
  • 在我的代码中,属性确实有两个双引号而不是一个。让我尝试修复 sn-p。无论哪种情况,我仍然收到我提到的错误。
  • 我认为XmlSerializerSystem.Configuration 的约定一无所知,在本例中为&lt;add&gt;(和&lt;remove&gt;)。尝试将IXmlSerializable 实现添加到Product 和/或ProductCollection,就像为Departments 所做的那样。

标签: c# xml


【解决方案1】:

更新:长话短说 - 错误的序列化程序。

除了特定的反序列化方法之外,大部分代码都可以工作。

我能够纠正原来的解决方案(here 是我找到灵感的地方)。

我已更正且有效的部分(不要忘记来自原始 post 的 IXmlSerializable):

private static string sectionName = "departments";

public static Departments Deserialize(string xml)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(Departments), new XmlRootAttribute(sectionName));
        Departments departments = null;

        var xdoc = XDocument.Parse(xml);
        departments = (Departments)serializer.Deserialize(xdoc.CreateReader());

        //var serializer = new XmlSerializer(typeof(Departments));
        //using (var reader = new StringReader(xml))
        //{
        //    departments = (Departments)(serializer.Deserialize(reader));
        //}

        return departments;
    }

希望它能帮助您继续前进。至少可以在我的机器上运行。如果需要完整的代码清单,请告诉我。

[保留原始解决方案,简单阅读建议的有问题的 xml 块]

  • 测试客户端。

class Program
{
    static void Main(string[] args)
    {
        string str = @"<departments>
                          <department id = ""1"" name = ""Sporting Goods"">
                               <products>
                                 <product name=""Basketball"" price=""9.99"">
                                      <add key = ""Color"" value = ""Orange""/>
                                      <add key = ""Brand"" value = ""[BrandName]""/>
                                 </product>
                               </products>
                          </department>
                       </departments>";

        XmlDocument xmlDoc = LoadXmlsFromString(str);

        string productXpath = "descendant-or-self::product";
        var nodes = ExtractNodes(xmlDoc, productXpath);
        foreach (XmlNode childrenNode in nodes)
        {
            var node = childrenNode.SelectSingleNode("descendant-or-self::*")?.OuterXml;
            var obj = Product.Deserialize(node);
        }
    }

    private static XmlNodeList ExtractNodes(XmlDocument xmlDoc, string xpath)
    {
        var nodes = xmlDoc.SelectNodes(xpath);
        return nodes;
    }

    private static XmlDocument LoadXmlsFromString(string str)
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(str);
        return xmlDoc;
    }
}
  • 通过为添加元素列表(以及要转换为的元素对象)添加 [XmlElement("add")] 属性来实现读取产品。

[Serializable, XmlRoot("add")]
public class KeyValue
{
    [XmlAttribute(AttributeName = "key")]
    public string Key { get; set; }
    [XmlAttribute(AttributeName = "value")]
    public string Value { get; set; }

    public static KeyValue Deserialize(string xml)
    {
        KeyValue keyValue = null;
        var serializer = new XmlSerializer(typeof(KeyValue));
        using (var reader = new StringReader(xml))
        {
            keyValue = (KeyValue)(serializer.Deserialize(reader));
        }
        return keyValue;
    }
}

[Serializable, XmlRoot("product")]
public class Product
{
    [XmlAttribute(AttributeName = "name")]
    public string Name { get; set; }
    [XmlAttribute(AttributeName = "price")]
    public decimal Price { get; set; }
    [XmlElement("add")]
    public List<KeyValue> KeyValueList { get; set; }

    public static Product Deserialize(string xml)
    {
        Product product = null;
        var serializer = new XmlSerializer(typeof(Product));
        using (var reader = new StringReader(xml))
        {
            product = (Product)(serializer.Deserialize(reader));
        }

        return product;
    }
}

【讨论】:

  • 我试过这个。然而,这对我来说并不成功。如 SO 问题所示,Product 在我的对象层次结构中更深。出于这个原因,我不能直接打电话给Deserialize。在我的场景中,Product 正在正确序列化/反序列化,KeyValueList 属性除外。太奇怪了。我不明白为什么在序列化/反序列化过程中没有包含它。
  • @zachTempleton,知道了,谢谢回复。我能够在原始代码中找到根本原因并能够引入修复程序(请参阅答案更新)。应该与 XDocument 一起使用另一个 XmlReader,而不是 TextReader。那行得通。请检查并告诉我。
【解决方案2】:

在我看来,您必须阅读的配置不需要XmlSerializerSystem.Configuration 为您提供的所有工具都绰绰有余。这里我重写了你的配置类(基本结构):

public class DepartmentsConfiguration : ConfigurationSection
{
    [ConfigurationProperty("departments", IsRequired = false, IsDefaultCollection = true)]
    public DepartmentItemCollection Departments
    {
        get
        {
            var departments = this["departments"] as DepartmentItemCollection;
            return departments;
        }
        set
        {
            this["departments"] = value;
        }
    }
}

[ConfigurationCollection(typeof(DepartmentItemCollection), AddItemName = "department")]
public class DepartmentItemCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new Department();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((Department)element).Name;
    }
}

public class Department : ConfigurationElement
{
    [ConfigurationProperty("id", IsRequired = false, IsKey = true)]
    public int Id
    {
        get
        {
            return (int)(this["id"]);
        }
        set
        {
            this["id"] = value;
        }
    }

    [ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]
    public string Name
    {
        get
        {
            return (string)(this["name"]);
        }
        set
        {
            this["name"] = value;
        }
    }

    [ConfigurationProperty("products", IsRequired = false, IsKey = false, IsDefaultCollection = false)]
    public ProductCollection Products
    {
        get
        {
            return (ProductCollection)this["products"];
        }
        set
        {
            this["products"] = value;
        }
    }
}

[ConfigurationCollection(typeof(ProductCollection), AddItemName = "product")]
public class ProductCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new Product();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((Product)element).Name;
    }
}

public class Product : ConfigurationElement
{
    [ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]
    public string Name
    {
        get
        {
            return (string)(this["name"]);
        }
        set
        {
            this["name"] = value;
        }
    }

    [ConfigurationProperty("price", IsRequired = false)]
    public decimal Price
    {
        get
        {
            return (decimal)(this["price"]);
        }
        set
        {
            this["price"] = value;
        }
    }

    [ConfigurationProperty("", IsRequired = false, IsKey = false, IsDefaultCollection = true)]
    [ConfigurationCollection(typeof(KeyValueConfigurationCollection), AddItemName = "add")]
    public KeyValueConfigurationCollection Items
    {
        get
        {
            var items = base[""] as KeyValueConfigurationCollection;
            return items;
        }
        set
        {
            base[""] = value;
        }
    }
}

App.config 中的&lt;configSections&gt;

  <configSections>
    <section 
        name="departmentConfiguration" 
        type="Test.DepartmentsConfiguration, Test" 
        allowLocation="true" 
        allowDefinition="Everywhere"
    />
  </configSections>
  <departmentConfiguration>
    <departments>
      <department id="1" name="Sporting Goods">
        <products>
          <product name="Basketball" price="9.99">
            <add key="Color" value="Orange" />
            <add key="Brand" value="[BrandName]" />
          </product>
        </products>
      </department>
    </departments>
  </departmentConfiguration>

以及如何使用ConfigurationManager阅读它:

DepartmentsConfiguration config = (DepartmentsConfiguration) ConfigurationManager
    .GetSection("departmentConfiguration");

foreach (Department department in config.Departments)
{
    Console.WriteLine($"{department.Id}, {department.Name}");

    foreach (Product product in department.Products)
    {
        Console.WriteLine($"{product.Name}, {product.Price}");

        foreach (string key in product.Items.AllKeys)
        {
            Console.WriteLine($"{key} -> {product.Items[key].Value}");
        }
    }
}

我知道这与您的问题不一致,请作为个人建议。

【讨论】:

    【解决方案3】:

    使用System.Xml.LinqXDocument怎么样?

    个人优点:

    • 简单
    • 没有第三者
    • 易于阅读,适用于较小的文件
    • 允许 Linq 语法

    个人缺点:

    • 倾向于在更大的文件中使用意大利面条式代码
    • 不是最快的解决方案

    示例

    static void Main(string[] args)
    {
    var xml =
    @"<departments>
    <department id=""1"" name=""Sporting Goods"">
    <products>
    <product name=""Basketball"" price=""9.99"">
    <add key=""Color"" value=""Orange"" />
    <add key=""Brand"" value=""[BrandName]"" />
    </product>
    </products>
    </department>
    </departments>";
    
    var xDoc = XDocument.Load(new StringReader(xml));
    
    var adds = xDoc.Root.Elements("department")
                        .Elements("products")
                        .Elements("product")
                        .Elements("add")
                        .Select(s => new KeyValuePair<string, string>(s.Attribute("key").ToString(), s.Attribute("value").ToString()))
                        .ToList();
    
    foreach (var department in xDoc.Root.Elements("department"))
    {
        Console.WriteLine("department: {0}", department.Attribute("name"));
        foreach (var products in department.Elements("products"))
        {
            foreach (var product in products.Elements("product"))
            {
                Console.WriteLine("product: {0}", product.Attribute("name"));
                foreach (var config in product.Elements("add"))
                {
                    Console.WriteLine("add: {0} -> {1}", config.Attribute("key"), config.Attribute("value"));
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-24
      • 1970-01-01
      • 2015-04-24
      • 1970-01-01
      • 2020-03-30
      • 1970-01-01
      • 2011-07-29
      相关资源
      最近更新 更多