Click here to Skip to main content
15,881,139 members
Articles / Programming Languages / C#

Json Parser, Viewer and Serializer

Rate me:
Please Sign up or sign in to vote.
4.71/5 (10 votes)
18 Jul 2014CPOL5 min read 30.5K   3K   25   6
A custom Json state machine parser, viewer and serializer for Json data

Introduction

I know that there are several Json parsers for C# but I like to understand stuff by doing it on my own. So when I needed to explore some server output in Json format and considering that Json really is a very simple structure, I decided so create a custom parser reading it. This is the core of my tool to show Json data in a tree view.

Image 1

In addition, I wanted to be able to (de-)serialize Json data from and into C# classes. Thus I wrote a custom serialization class which can handle different scenarios without a whole bunch of initialization or attributes.

Background

Json example

This is just an example to show what Json data looks like - this is formatted, when using server output as less white chars as possible might be used:

JavaScript
{
    "PossibleValues" : {
        "ObjectValue" : { 
            "OnlyValue" : "This is the only value in here" 
        },
        "ArrayValue" : [
            1, 1, 2, 3, 5, 8, 13, 21, "..."
        ],
        "StringValue" : "String",
        "NumberValue" : 3.1415926536,
        "BooleanValue" : true,
        "NullValue" : null
    }, 
    "Lightspeed" : {
        "Value" : 2.99792458e8,
        "Unit" : "m/s"        
    }
} 

Representing the Json structure

Json supports six types of data:

  • Object: Is enclosed in bracelets '{ }' and has name-value content where names and values are divided by a colon ':' and name-value pairs are divided by comma ','. In code this will be represented with a BjSJsonObject class instance implementing a BjSJsonObjectValue List to represent the name-value pairs.
  • Array: Is enclosed in brackets '[ ]' and has values as content divided by comma ','. In code this will be represented with a BjSJsonArray class instance implementing an object List.
  • String: Is enclosed by quotes '"'. In code this is a String.
  • Number: Is something like -1.454324e-18 or a simpler form of that. In code this is represented as a Decimal so that a maximum precision is kept.
  • Boolean: Is the word 'true' or 'false' without quotes. In code this is a Boolean.
  • Null: Is the word 'null' without quotes. In code this is null.

A Json object as well as an array can contain different types of data so that a name-value pairs and the arrays only contain C# objects storing the actual values. To determine which kind of value is stored you can use the is operator or an enumerated value description as BjSJsonValueKind from the helper function BjSJsonHelper.GetValueKind(...).

The Json parser state machine

The parser is a state machine which reads the input char by char. It's pretty straight forward switching states whenever a new part begins, so I won't go any deeper here - the rest can be seen in the code at BjSJsonHelper.BjSJsonReader.

The Json (De-)Serializer

This basically has only two public methods:

  • ToJson<T>(T obj): Serializes a class of a generic Type into a BjSJsonObject - all subtypes, arrays, Lists and Dictionaries are handled automatically.
  • FromJson<T>(BjSJsonObject obj): Maps a BjSJsonObject into an instance of the generic Type T - here all subtypes, arrays, Lists and Dictionaries are handled automatically, too.

The serializing method ToJson<T>(T obj) loops through the properties of given Type of T and tries to extract each value and to convert it to a Json representation.

The deserializing method FromJson<T>(BjSJsonObject obj) loops through the properties of the BjSJsonObject and tries to find a Property in the given Type of T with the same name and an appropriate data type. If that is found, the Json value is converted into the data type of the class property.

There are some limitations though as I manually manage the serialization of data types. So this serializer only supports classes with a standard constructor (also for subclasses) and only properties with the following data types:

  • null
  • String
  • Numbers (byte, sbyte, short, ushort, int, uint, long, ulong, float, double and decimal)
  • Guid
  • DateTime (is converted into an ISO formatted string)
  • TimeSpan (is converted into a formatted string)
  • Image|Bitmap (is converted into a Base64 string)
  • Array (one-dimentional, all T[] where T is one of these data types)
  • List<T> (where T is one of there data types, is converted into an array - the type definition of the property separates between Array and List when deserializing)
  • Dictionary<K,V> (where K and V are each one of these data types, is converted into an array of two-value arrays)
  • class instance (with standard constructor and only properties of these data types)

Other data types I tried to skip - but I haven't tested that as these are all data types I need to use.

Using the code

The main class used for handling Json objects is BjSJsonObject. BjSJsonObjectMember and BjSJsonArray are used within BjSJsonObject and build up the data types which are not represented by simple C# classes. The BjSJsonConverter class can be used to convert C# classes into Json objects and back.

Image 2

The usage is pretty staight forward. To load a Json obect just use one of the construcors:

C#
// Load a string
string data = "{\"Member\":\"Value\",\"Another\":3.14}";
BjSJsonObject jObj = new BjSJsonObject(data);

// Load a file directly
string filename = @"C:\data.json";
BjSJsonObject jObj = new BjSJsonObject(filename, Encoding.ASCII);

// Load data from a stream
MemoryStream ms = new MemoryStream(File.ReadAllBytes(filename));
BjSJsonObject jObj = new BjSJsonObject(new StreamReader(ms));

To turn it back to Json text just call the ToJsonString(bool) method:

C#
BjSJsonObject jObj = new BjSJsonObject(@"C:\data.json", Encoding.ASCII);

// Strip as many white spaces as possible
string jsonData = jObj.ToJsonString(true);

// Format the output to make it easily readable
string jsonData = jObj.ToJsonString(false);  

You can access and alter data by using the indexes, Add(), Remove() and RemoveAt() methods. Also BjSJsonObject and BjSJsonArray implement the interface IEnumerator and offer a Count property, so that for and foreach loops can also be used. The following example shows how a BjSJsonObject is created, filled with data that is used to add other properties before the result is saved to a file:

C#
// Create a new BjSJsonObject
BjSJsonObject jObj = new BjSJsonObject();

// Create a BjSJsonArray, fill it with numbers and add it to the object as property "arr"
BjSJsonArray jArr = new BjSJsonArray();
for (int i = 0; i < 10; i++)
    jArr.Add(Convert.ToDecimal(i));
jObj.Add("arr", jArr);

// Add the square of each array number as a property with the number as name to the object
foreach (object n in jArr)
    jObj.Add(n.ToString(), (decimal)n * (decimal)n);

// Convert the object to text
string jData = jObj.ToJsonString(false);

// Save the data to a file
File.WriteAllText(@"C:\output.json", jData);

The BjSJsonConverter can only be used very straight forward - as intended. Passed objects are handled recursively. Besides the object itself the only other information used is the objects type. For converting to Json this is only used to have the same method pattern as for when converting back. The following exaple defines and creates an instance of the Customer class, converts is to Json text and back into a second class instance:

C#
public class Customer
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Dictionary<string, Address> Addresses { get; set; }
    public List<Customer> SubCustomers { get; set; }
}
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

public void DoConverting()
{
    // Create test data
    Customer c1 = new Customer()
    {
        Id = Guid.NewGuid(),
        Name = "Max Mustermann",
        Addresses = new Dictionary<string, Address>()
        {
            { "Work", new Address() { Street = "Bahnhofstr. 1", City = "12345 Neustadt" },
            { "Home", new Address() { Street = "Postweg 34", City = "12345 Neustadt" }
        },
        SubCustomers = new List<Customer>()
        {
            new Customer()
            {
                Id = Guid.NewGuid(),
                Name = "Tanja Müller",
                Addresses = null,
                SubCustomers = null
            },
            new Customer()
            {
                Id = Guid.NewGuid(),
                Name = "Steffan Taylor",
                Addresses = null,
                SubCustomers = null
            }
        }
    }

    // Convert the object to Json text
    string jText = BjSJsonConverter.ToJson(c1).ToJsonString(true);

    // Convert the Json text back into another instance of Customer
    BjSJsonObject jObj = new BjSJsonObject(jText);
    Customer c2 = BjSJsonConverter.FromJson<Customer>(jObj);
} 

The JsonViewer project is only a test scenario and a tool I use.

Points of Interest

The conversion code in the BjSJsonConverter is pretty dynamic. It could easily be used for other purposes like a ORM. I definitely recommend to have a look at it to anyone how wants to start working on .NET Reflection.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
I'm working mainly on .NET Compact Framework C# on mobile devices at work. At home it's .NET Full Framework C# and a bit JavaScript.

Comments and Discussions

 
QuestionOmit null or empty properties? Pin
Johnny J.27-Nov-23 0:06
professionalJohnny J.27-Nov-23 0:06 
QuestionThanks Bjørn Pin
Francisco R M10-Nov-23 8:47
Francisco R M10-Nov-23 8:47 
GeneralMy vote of 5 Pin
Tom Mok5-May-23 20:02
Tom Mok5-May-23 20:02 
QuestionAPI Rest c# et Json Pin
Jalloh23-Jul-20 1:16
Jalloh23-Jul-20 1:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.