Search code examples
c#xmljsonxml-parsingxmlconvert

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


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#.


Solution

  • 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: