Skip to content

Instantly share code, notes, and snippets.

@thekid
Last active January 13, 2016 00:09
Show Gist options
  • Select an option

  • Save thekid/b9ab4b874fd01f95dd66 to your computer and use it in GitHub Desktop.

Select an option

Save thekid/b9ab4b874fd01f95dd66 to your computer and use it in GitHub Desktop.
JSON in Mono and .NET

In a project of mine, I needed to parse Composer's composer.json files using C#. I wanted to make this work on both Windows and Unix systems, using Microsoft's .NET Framework on Windows and Mono inside my Ubuntu virtualbox.

I was parsing the JSON using the DataContractJsonSerializer class, which I'd instantiated and used as follows:

var settings = new DataContractJsonSerializerSettings {  
    UseSimpleDictionaryFormat = true  
};
var json = new DataContractJsonSerializer(typeof(Composer), settings);

// Read from a stream
var definitions = json.ReadObject(input) as Composer;

Funny enough, you need to cast to the type you've created the instance with...

When I first ran this in Mono, I was seeing ArgumentNullExceptions that "rootName" cannot be null. An easy fix, I guessed. It turned out to be quite an odyssey.

I can't believe this

First thing I thought is I was in error. Maybe I was relying on not initializing all settings members, so I did that. With more interesting effects, but not the one I wanted to see: That it would parse the file like on Windows.

I went on a long hunt, first focussing on finding clues as to what I was doing wrong (using the error message to start with, and gradually deleting and refining the query when everything turning up was not being helpful), then second on the Mono codebase (concentrating on issues in the beginning, continuing with searching the source code), and finally on to an internet search on alternatives. After about two and a half hours of frustrating research work, my conclusions are:

  • There are lots of questions about C# and JSON
  • There are several different builtin options, Windows.Data.Json, System.Json, one in System.Web or so, and the data contract Serialization APIs above. Not all are always available in all installations, some are considered deprecated, and all have different APIs.
  • However, everyone seems to use a third-party library like Newtonsoft.Json, JSON.net, JSONSharp and so on, with different licences, binary footprint, ways of doing things.

All I wanted to do is the rough equivalent of JSON.parse(), why is this such a pain in the ass?

Giving up?

The next thought I had was to stray away from the ready-to-run builtin things the .NET framework provides and that I'd come to love using (is there anything LINQ cannot do?) and to go down the stony road of implementing it all by myself. However, I quickly felt disheartened when I read the stackoverflow discussions on string tokenizers in C# - seems you either end up with String.Split() or with a full-fledged ANTLR port.

Luckily, I found something inbetween. Inspired by this Stackoverflow answer, I started working with JsonReaderWriterFactory.CreateJsonReader() (which interestengly returns an XmlDictionaryReader instance!). This way, I wasn't taking care of the low-level I/O stuff, but looking at its extensive seemed to offer the flexibility I need to work around the issue I mentioned before.

Transforming the Reader

The XmlReader from actually makes you work sequentially with XML data (or, because we're using something we got back from a call asking for a JSON reader, something idiomatically similar). The basic structure is a do ... while loop somewhere along the lines of:

do
{
    if (XmlNodeType.Text == input.NodeType)
    {
        // A leaf, with its value inside input.Value
    }
    else if (XmlNodeType.Element == input.NodeType)
    {
        // A key, with its name inside input.LocalName. For arrays,
        // this is always "item". Attributes will contain
        if (input.MoveToFirstAttribute())
        {
            do
            {
                // Pitfall alert: Windows .NET has object key in an 
                // attribute named "item" in input.LocalName, *except*
                // when handling the root object. Mono handles this
                // differently: object keys are returned as names!
            } while (input.MoveToNextAttribute());

            input.MoveToElement();
        }
    }
    else if (XmlNodeType.EndElement == input.NodeType)
    {
        // The end of a key
    }
} while (input.Read());

I could implement this by yielding the values from inside the first if branch, and updating a path variable (think: filesystem) in the other two. Plus of course the specialcase handling for the slight .NET / Mono differences. I then put this into a method called StructureOf and made it return IEnumerable<KeyValuePair<string, string>>.

After turning this into a Lookup this gives us the following:

root\name = [xp-framework/unittest]
root\type = [library]
root\homepage = [http://xp-framework.net/]
root\license = [BSD-3-Clause]
root\description = [Unittests for the XP Framework]
root\keywords\item = [module, xp]
root\require\xp-framework/core = [^6.10]
root\require\xp-framework/io-collections = [^6.5]
root\require\php = [>=5.5.0]
root\bin\item = [bin/xp.xp-framework.unittest.test]
root\autoload\files\item = [src/main/php/autoload.php]

I'm using a backslash to concatenate the keys, since this never appears in composer.json keys - OK for the moment though I do feel slightly dirty for doing it:)

Putting it together

Now the last thing I'm left to do is to read the values from the lookup instance, and set the object members accordingly:

var definitions = new Composer();
definitions.Name = lookup[@"root\name"].FirstOrDefault();
definitions.Require = lookup
    .Where(pair => pair.Key.StartsWith(@"root\require\"))
    .ToDictionary(value => value.Key.Substring(@"root\require\".Length), value => value.First())
;

(Did I say I love the Linq classes?)

And voilà, this is good enough for parsing this simple file. No external dependencies, works on both platform and it's only a couple of lines of code.

Here's where I started out, this is what I came up with and this is difference between them.

Hope this helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment