Search code examples
c#jsonxmljson.net

Convert XML to JSON and then deserialize to object


I'm developing an ASP.NET MVC application with .Net Framework 4.7 and JSON.NET 10.0.2.

I want to load a xml document, convert it to JSON and then deserialize to object. I want to do this because I want to use JSON files and also XML files.

This is the class I want to create with the file:

public class ProductionOrderFile
{
    public string ProductionOrderName { get; set; }
    public string ProductCode { get; set; }
    public List<Batch> Batches { get; set; }
    public List<AggregationLevelConfiguration> Levels { get; set; }
    public List<VariableData> VariableData { get; set; }
}

This is the class that deserialize to the object:

private ProductionOrderFile ParseProductionOrderFile(Stream inputStream)
{
    var serializer = new JsonSerializer();
    XmlDocument doc = new XmlDocument();
    doc.Load(inputStream);

    string jsonText = JsonConvert.SerializeXmlNode(doc);

    return JsonConvert.DeserializeObject<ProductionOrderFile>(jsonText);
}

This is the XML document:

<ProductionOrderFile>
    <ProductionOrderName>"ProOrd_Xml_001"</ProductionOrderName>
    <ProductCode>Pro_EU_001</ProductCode>
    <Batches>
        <Name>Lote_Xml_01</Name>
    </Batches>
    <Levels>
        <Id>1</Id>
        <Name>Nivel_1</Name>
        <PkgRatio>120</PkgRatio>
    </Levels>
    <Levels>
        <Id>2</Id>
        <Name>Nivel_2</Name>
        <PkgRatio>1</PkgRatio>
    </Levels>
    <VariableData>
        <VariableDataId>01</VariableDataId>
        <LevelId>1</LevelId>
        <Value>Pro_EU_001</Value>
    </VariableData>
    <VariableData>
        <VariableDataId>20</VariableDataId>
        <LevelId>1</LevelId>
        <Value>Lote_Xml_01</Value>
    </VariableData>
    <VariableData>
        <VariableDataId>11</VariableDataId>
        <LevelId>1</LevelId>
        <Value>170101</Value>
    </VariableData>
    <VariableData>
        <VariableDataId>17</VariableDataId>
        <LevelId>1</LevelId>
        <Value>210101</Value>
    </VariableData>
    <VariableData>
        <VariableDataId>21</VariableDataId>
        <LevelId>1</LevelId>
        <Value>####################</Value>
    </VariableData>
</ProductionOrderFile>

And this is the jsonText var content:

{
    "ProductionOrderFile": {
        "ProductionOrderName": "\"ProOrd_Xml_001\"",
        "ProductCode": "Pro_EU_001",
        "Batches": {
            "Name": "Lote_Xml_01"
        },
        "Levels": [{
            "Id": "1",
            "Name": "Nivel_1",
            "PkgRatio": "120"
        }, {
            "Id": "2",
            "Name": "Nivel_2",
            "PkgRatio": "1"
        }],
        "VariableData": [{
            "VariableDataId": "01",
            "LevelId": "1",
            "Value": "Pro_EU_001"
        }, {
            "VariableDataId": "20",
            "LevelId": "1",
            "Value": "Lote_Xml_01"
        }, {
            "VariableDataId": "11",
            "LevelId": "1",
            "Value": "170101"
        }, {
            "VariableDataId": "17",
            "LevelId": "1",
            "Value": "210101"
        }, {
            "VariableDataId": "21",
            "LevelId": "1",
            "Value": "####################"
        }]
    }
}

But this return JsonConvert.DeserializeObject<ProductionOrderFile>(jsonText); returns an instance of ProductionOrderFile with all of its properties null.

Maybe the problem is that the XML document doesn't have the right format.

Has the XML document the right format? What am I doing wrong?


Solution

  • The easiest way to deserialize your XML is to do so in one step using XmlSerializer. You can do this by marking your List<T> properties with [XmlElement] to indicate they should be serialized as a collection of repeated elements without an outer container element:

    public class ProductionOrderFile
    {
        public string ProductionOrderName { get; set; }
        public string ProductCode { get; set; }
        [System.Xml.Serialization.XmlElement]
        public List<Batch> Batches { get; set; }
        [System.Xml.Serialization.XmlElement]
        public List<AggregationLevelConfiguration> Levels { get; set; }
        [System.Xml.Serialization.XmlElement]
        public List<VariableData> VariableData { get; set; }
    }
    

    And then do:

    private ProductionOrderFile ParseProductionOrderFile(Stream inputStream)
    {
        var serializer = new XmlSerializer(typeof(ProductionOrderFile));
        return (ProductionOrderFile)serializer.Deserialize(inputStream);
    }
    

    Sample fiddle #1.

    That being said, if you insist on a two-step deserialization process using an intermediate XmlDocument, you need to modify your code as follows:

    1. Omit the root element by using JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.None, true) when creating your intermediate jsonText.

      This removes the root "ProductionOrderFile": { ... } property that does not appear in ProductionOrderFile and bubbles the nested properties up to the top level.

    2. Force the <Batches>, <Levels> and <VariableData> element(s) to be converted to JSON as arrays by following the instructions from Convert XML to JSON and force array.

    Thus your code becomes:

    private ProductionOrderFile ParseProductionOrderFile(Stream inputStream)
    {
        // var serializer = new JsonSerializer(); Not needed
        XmlDocument doc = new XmlDocument();
        doc.Load(inputStream);
    
        foreach (var xPath in new [] { "//Batches", "//Levels", "//VariableData" })
        {
            foreach (var node in doc.SelectNodes(xPath).Cast<XmlElement>())
            {
                node.SetAttribute("xmlns:json", "http://james.newtonking.com/projects/json");
                node.SetAttribute("Array", "http://james.newtonking.com/projects/json", XmlConvert.ToString(true));
            }
        }
    
        string jsonText = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.None, true);
    
        return JsonConvert.DeserializeObject<ProductionOrderFile>(jsonText);
    }
    

    Sample fiddle #2.