C#/.Net -Fast way to Parse XML / XML -> Json

4.5k views Asked by At

I'm new to XML, since now I've only worked with Json:( I have a XML file which looks like this:

<AdapterCards>
    <cards type="MCS">
        <card>
            <id>id1</id>
            <description>desc1</description>
            <mccode>code1</mccode>
        </card>
        <card>
            <id>id2</id>
            <description>desc2</description>
            <mccode>code2</mccode>
        </card>
    </cards>
    <cards type="MCM">
        <card>
            <id>id3</id>
            <description>desc3</description>
            <mccode>code3</mccode>
        </card>
        <card>
            <id>id4</id>
            <description>desc4</description>
            <mccode>code4</mccode>
        </card>
    </cards>
    <cards type="F"/>
    <cards type="B"/>
</AdapterCards>

I want to parse it to a json string which should look like this:

{[{'type': 'mcs', 'id': 'id1', 'description': 'desc1', 'mccode': 'code1'},
  {'type': 'mcs', 'id': 'id2', 'description': 'desc2', 'mccode': 'code2'},
  {'type': 'mcm', 'id': 'id3', 'description': 'desc3', 'mccode': 'code3'},
  {'type': 'mcm', 'id': 'id4', 'description': 'desc4', 'mccode': 'code4'}
]}

My problem is that I haven't worked with XML at all (yep, shame on me). Could you please give me some leads on how to parse the xml in a fast way (I have it on a Stream, I uploaded it on server). I have searched for some XML to Json converters, but it's impossible to fine one to suit my needs, since I need a "special" format.

Thank you for the answers:)! I'm using C#.

4

There are 4 answers

0
Codesleuth On BEST ANSWER

I had to write a bespoke solution for the same type of thing myself recently. I did it with XSLT, using the XslCompiledTransform class to run an input of XML and output JSON.

This needs some work but should help you with the basics (it is copy-pasted from the work I did, changed to almost suit your needs):

AdapterCards.XML

<AdapterCards>
    <cards type="MCS">
        <card>
            <id>id1</id>
            <description>desc1</description>
            <mccode>code1</mccode>
        </card>
        <card>
            <id>id2</id>
            <description>desc2</description>
            <mccode>code2</mccode>
        </card>
    </cards>
    <cards type="MCM">
        <card>
            <id>id3</id>
            <description>desc3</description>
            <mccode>code3</mccode>
        </card>
        <card>
            <id>id4</id>
            <description>desc4</description>
            <mccode>code4</mccode>
        </card>
    </cards>
    <cards type="F"/>
    <cards type="B"/>
</AdapterCards>

AdapterCards.XSL

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="no" omit-xml-declaration="yes" method="text" encoding="UTF-8" media-type="text/x-json" />

<xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

    <!-- Main template for escaping strings; used by above template and for object-properties 
       Responsibilities: placed quotes around string, and chain up to next filter, escape-bs-string -->
    <xsl:template name="escape-string">
        <xsl:param name="s"/>
        <xsl:text>"</xsl:text>
        <xsl:call-template name="escape-bs-string">
            <xsl:with-param name="s" select="$s"/>
        </xsl:call-template>
        <xsl:text>"</xsl:text>
    </xsl:template>

    <!-- Escape the backslash (\) before everything else. -->
    <xsl:template name="escape-bs-string">
        <xsl:param name="s"/>
        <xsl:choose>
            <xsl:when test="contains($s,'\')">
                <xsl:call-template name="escape-quot-string">
                    <xsl:with-param name="s" select="concat(substring-before($s,'\'),'\\')"/>
                </xsl:call-template>
                <xsl:call-template name="escape-bs-string">
                    <xsl:with-param name="s" select="substring-after($s,'\')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="escape-quot-string">
                    <xsl:with-param name="s" select="$s"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!-- Escape the double quote ("). -->
    <xsl:template name="escape-quot-string">
        <xsl:param name="s"/>
        <xsl:choose>
            <xsl:when test="contains($s,'&quot;')">
                <xsl:call-template name="encode-string">
                    <xsl:with-param name="s" select="concat(substring-before($s,'&quot;'),'\&quot;')"/>
                </xsl:call-template>
                <xsl:call-template name="escape-quot-string">
                    <xsl:with-param name="s" select="substring-after($s,'&quot;')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="encode-string">
                    <xsl:with-param name="s" select="$s"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!-- Replace tab, line feed and/or carriage return by its matching escape code. Can't escape backslash
       or double quote here, because they don't replace characters (&#x0; becomes \t), but they prefix 
       characters (\ becomes \\). Besides, backslash should be seperate anyway, because it should be 
       processed first. This function can't do that. -->
    <xsl:template name="encode-string">
        <xsl:param name="s"/>
        <xsl:choose>
            <!-- tab -->
            <xsl:when test="contains($s,'&#x9;')">
                <xsl:call-template name="encode-string">
                    <xsl:with-param name="s" select="concat(substring-before($s,'&#x9;'),'\t',substring-after($s,'&#x9;'))"/>
                </xsl:call-template>
            </xsl:when>
            <!-- line feed -->
            <xsl:when test="contains($s,'&#xA;')">
                <xsl:call-template name="encode-string">
                    <xsl:with-param name="s" select="concat(substring-before($s,'&#xA;'),'\n',substring-after($s,'&#xA;'))"/>
                </xsl:call-template>
            </xsl:when>
            <!-- carriage return -->
            <xsl:when test="contains($s,'&#xD;')">
                <xsl:call-template name="encode-string">
                    <xsl:with-param name="s" select="concat(substring-before($s,'&#xD;'),'\r',substring-after($s,'&#xD;'))"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$s"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>


    <xsl:template match="card">
        <xsl:text>{</xsl:text>

        <xsl:text>"type":</xsl:text>
        <xsl:call-template name="escape-string">
            <xsl:with-param name="s" select="translate(../@type, $uppercase, $smallcase)"/>
        </xsl:call-template>

        <xsl:text>,"id":</xsl:text>
        <xsl:call-template name="escape-string">
            <xsl:with-param name="s" select="id"/>
        </xsl:call-template>

        <xsl:text>,"description":</xsl:text>
        <xsl:call-template name="escape-string">
            <xsl:with-param name="s" select="description"/>
        </xsl:call-template>

        <xsl:text>,"mccode":</xsl:text>
        <xsl:call-template name="escape-string">
            <xsl:with-param name="s" select="mccode"/>
        </xsl:call-template>

        <xsl:if test="following::card">},</xsl:if>
        <xsl:if test="not(following::card)">}</xsl:if>
    </xsl:template>


    <xsl:template match="/AdapterCards">
        <xsl:text>{[</xsl:text>

        <xsl:apply-templates select="cards/card" />

        <xsl:text>]}</xsl:text>
  </xsl:template>
</xsl:stylesheet>

C#

// Load XML document
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("AdapterCards.XML");

// Transform the XML into JSON
XslCompiledTransform transformer = new XslCompiledTransform();
using (var xslStylesheetFile = File.Open("AdapterCards.XSL", FileMode.Open))
{
    using (var xmlReader = new XmlTextReader(xslStylesheetFile))
    {
        transformer.Load(xmlReader);
    }
}
var sourceNavigator = xmlDoc.CreateNavigator();
using (MemoryStream ms = new MemoryStream())
{
    transformer.Transform(sourceNavigator, null, ms);
    ms.Position = 0;
    using (var sr = new StreamReader(ms))
    {
        return sr.ReadToEnd(); // <-- this is your JSON
    }
}

I executed the above XSL in Notepad++ and got the following:

{[{"type":"mcs","id":"id1","description":"desc1","mccode":"code1"},
{"type":"mcs","id":"id2","description":"desc2","mccode":"code2"},
{"type":"mcm","id":"id3","description":"desc3","mccode":"code3"},
{"type":"mcm","id":"id4","description":"desc4","mccode":"code4"}]}

As you can see, there's commas missing where they need to be. But it's almost there!!

Updated the XSL and the output which now work with commas in the right place. Next thing to fix is the case of "type". I'm thinking you might need to use XSL 2.0 to gain access to the xpath function fn:lower-case().

EDIT3: Done - case is now translated to lower with the help of this answer.

References:

0
Nitheen Rao On
XDocument xDoc = XDocument.Load(queryURL);                               
var x1 = from el in xDoc.Root.Descendants("results").Descendants("div").Descendants("div").Descendants("span").Attributes("class")
where el.Value == "pr"
  select el.Parent.Value;                
   stockFeed.Price = Decimal.Parse(x1.First().ToString());

LINQ is the best way to extract the data.

2
Meherzad On

Using the JsonConvert class which contains helper methods for this precise purpose:

// To convert an XML node contained in string xml into a JSON string   
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
string jsonText = JsonConvert.SerializeXmlNode(doc);

// To convert JSON text contained in string json into an XML node
XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(json);

Documentation here: Converting between JSON and XML with Json.NET

0
Cinchoo On

Here is one another easy way to convert xml to json file using Cinchoo ETL, an open source library

using (var r = new ChoXmlReader("*** Xml file path ***")
       .WithXPath("//cards")
       )
{
    using (var w = new ChoJSONWriter("*** Json file path ***")
          )
        w.Write(r.SelectMany(r1 => ((dynamic[])r1.cards??new dynamic[]{}).Select(c => new { r1.type, c.id, c.description, c.mccode })));
}

Sample fiddle: https://dotnetfiddle.net/Yzcaiw