Search code examples
c#xmllinqlinq-to-xmlxml-serialization

C# Serializing an object into XML that includes a list of objects


I need to serialize an XML to attach for an API PUT request.

I am using System.Xml.Serialization.

The end result needs to resemble the following:

<?xml version="1.0" encoding="UTF-8"?>
<prestashop xmlns:xlink="http://www.w3.org/1999/xlink">
        <product>
            <id>
                <![CDATA[2678]]>
            </id>
            <cache_default_attribute>
                <![CDATA[0]]>
            </cache_default_attribute>
            <id_shop_default>
                <![CDATA[1]]>
            </id_shop_default>
            <reference>
                <![CDATA[2678]]>
            </reference>
            <supplier_reference>
                <![CDATA[]]>
            </supplier_reference>
            <location>
                <![CDATA[]]>
            </location>
            <width>
                <![CDATA[0.000000]]>
            </width>
            <height>
                <![CDATA[0.000000]]>
            </height>
            <depth>
                <![CDATA[0.000000]]>
            </depth>
            <weight>
                <![CDATA[0.000000]]>
            </weight>
            <quantity_discount>
                <![CDATA[0]]>
            </quantity_discount>
            <ean13>
                <![CDATA[]]>
            </ean13>
            <isbn>
                <![CDATA[]]>
            </isbn>
            <upc>
                <![CDATA[0]]>
            </upc>
            <mpn>
                <![CDATA[000.018]]>
            </mpn>
            <cache_is_pack>
                <![CDATA[0]]>
            </cache_is_pack>
            <cache_has_attachments>
                <![CDATA[0]]>
            </cache_has_attachments>
            <is_virtual>
                <![CDATA[0]]>
            </is_virtual>
            <state>
                <![CDATA[1]]>
            </state>
            <additional_delivery_times>
                <![CDATA[1]]>
            </additional_delivery_times>
            <delivery_in_stock>
                <language id="1">
                    <![CDATA[]]>
                </language>
                <language id="2">
                    <![CDATA[]]>
                </language>
            </delivery_in_stock>
            <delivery_out_stock>
                <language id="1">
                    <![CDATA[]]>
                </language>
                <language id="2">
                    <![CDATA[]]>
                </language>
            </delivery_out_stock>
            <product_type>
                <![CDATA[combinations]]>
            </product_type>
            <on_sale>
                <![CDATA[0]]>
            </on_sale>
            <online_only>
                <![CDATA[0]]>
            </online_only>
            <date_add>
                <![CDATA[2023-02-16 00:21:12]]>
            </date_add>
            <date_upd>
                <![CDATA[2023-02-22 17:22:23]]>
            </date_upd>
            <name>
                <language id="1">
                    <![CDATA[ETNIES TRI LAM POLO (000.018)]]>
                </language>
                <language id="2">
                    <![CDATA[ETNIES TRI LAM POLO (000.018)]]>
                </language>
            </name>
        </product>
</prestashop>

For this purpose I have a class like this:

[XmlType("product")]
public class Product
{
    public Product()
    {
        id = "";
        active = "1";
        available_for_order= "1";
        indexed= "1";
        visibility = "both";
        delivery_in_stock = new List<PaLanguage>();
        delivery_out_stock = new List<PaLanguage>();
        name = new List<PaLanguage>();
    }

    [XmlElement("id")] public XmlCDataSection C_id { get { return new XmlDocument().CreateCDataSection(id); } set { id = value.Value; } }
    [XmlIgnore] public string id;
    [XmlElement("cache_default_attribute")] public XmlCDataSection C_cache_default_attribute { get { return new XmlDocument().CreateCDataSection(cache_default_attribute); } set {cache_default_attribute = value.Value; } }
    [XmlIgnore] public string cache_default_attribute;
    ...
    [XmlArray("delivery_in_stock")] public List<PaLanguage> delivery_in_stock;
    [XmlArray("delivery_out_stock")] public List<PaLanguage> delivery_out_stock;
    ...

With PaLanguage being:

public class PaLanguage
{
    [XmlElement("language")] public XmlCDataSection C_language { get { return new XmlDocument().CreateCDataSection(language); } set { language = value.Value; } }
    [XmlIgnore] public string language { get; set; }
    [XmlAttribute("id")] public string id;
}

Because this is updating a record I first complete a get request and then match all of its values. I accomplish this with the following code:

XDocument getResponse = Helper.GetProductReference(get, Helper.GetValueByKey("ShopApi") + "products/");
var gotProduct = getResponse
    .Element("prestashop")
    .Element("products")
    .Elements("product")
    .Where(e => e.Element("reference").Value == mtrl)
    .Single();  

I create a new Product item to attach to my put request but I have trouble serializing its XmlArrays.

The last thing I tried was the following:

foreach (XElement x in gotProduct.Element("delivery_in_stock").Elements("language"))
{
    product.delivery_in_stock.Add(new PrestaLanguage { id = (string)x.Attribute("id"), language = (string)x.Element("language") });
}

Which is completely wrong because it serializes it as:

<product>
<id><![CDATA[2678]]></id>
... xml
<delivery_in_stock>
  <PaLanguage id="1">
    <language><![CDATA[]]></language>
  </PaLanguage>
  <PaLanguage id="2">
    <language><![CDATA[]]></language>
  </PaLanguage>
</delivery_in_stock>

It feels like I've tried everything one can find online and in official documentation and only get further from my intended result with each attempt.

I should note the class actually has dozens more fields and they're all either just a string or another combination of id and a language value.

Any and all help is appreciated.


Solution

  • I was on the right track with

    public class PaLanguage
    {
        [XmlElement("language")] public XmlCDataSection C_language { get { return new XmlDocument().CreateCDataSection(language); } set { language = value.Value; } }
        [XmlIgnore] public string language { get; set; }
        [XmlAttribute("id")] public string id;
    }
    

    First I changed C_language into an XmlNode[] with a slightly different getter and setter

    public class PaLanguage
    {
        [XmlText] public XmlNode[] C_language 
            { 
                get { var dummy = new XmlDocument();  return new XmlNode[] { dummy.CreateCDataSection(language) }; } 
                set { var dummy = (XmlCDataSection)value[0]; language = dummy.Data; } 
            }
        [XmlIgnore] public string language { get; set; }
        [XmlAttribute("id")] public string id;
    }
    

    Then I also changed the lists in Product

    [XmlArray("delivery_in_stock")] [XmlArrayItem("language")] public List<PaLanguage> delivery_in_stock;
    [XmlArray("delivery_out_stock")] [XmlArrayItem("language")] public List<PaLanguage> delivery_out_stock
    

    Finally as Sebastian recommended, I used Descendants instead of Elements.

    foreach (XElement x in gotProduct.Element("delivery_in_stock").Descendants("language"))
                            {
                                product.delivery_in_stock.Add(new PrestaLanguage { id = (string)x.Attribute("id"), language = (string)x.Element("language") });
                            }
                            foreach (XElement x in gotProduct.Element("delivery_out_stock").Descendants("language"))
                            {
                                product.delivery_out_stock.Add(new PrestaLanguage { id = (string)x.Attribute("id"), language = (string)x.Element("language") });
                            }
    

    Thanks to everyone that helped.