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

Small LINQ to JSON Library

Rate me:
Please Sign up or sign in to vote.
4.96/5 (28 votes)
6 Dec 2011CPOL13 min read 80.8K   2K   79  
A small LinqToJSON library in C#, and how it works
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ranslant.JSON.Linq;

namespace DemoApp
{
    class Program
    {
        static void Main(string[] args)
        {
            EncodingTests();

            // uncomment the line below to get the exception
            //Writetest2_MustTriggerException("testfail.json");   // won't trigger an exception: code is commented out. Remove the comment marker to get the exception
            
            WriteTest1_MustSucceed("test1.json");

            ReadTest_MustSucceed("test1.json", true);

            // uncomment the line below to get the exception
            //ReadTest_mustFail("testfail.json");   // failed as expected, until the json content was fixed in the file

            WriteReadTest_MustSucceed("WRtest.json");

        }

        private static void EncodingTests()
        {
            // write the data
            WriteJsonDataWithByteOrderMark("simpson_ascii.json", new ASCIIEncoding());
            WriteJsonDataWithByteOrderMark("simpson_utf8.json", new UTF8Encoding(true)); // use new UTF8Encoding(true) to get the UTF-8 3bytes ByteOrderMark 
            WriteJsonDataWithByteOrderMark("simpson_utf16LE.json", new UnicodeEncoding(false, true));
            WriteJsonDataWithByteOrderMark("simpson_utf16BE.json", new UnicodeEncoding(true, true));


            // read the data
            JDocument doc01 = JDocument.Load("simpson_ascii.json", null);
            JDocument doc02 = JDocument.Load("simpson_utf8.json", null);
            JDocument doc03 = JDocument.Load("simpson_utf16LE.json", null);
            JDocument doc04 = JDocument.Load("simpson_utf16BE.json", null);

            JDocument doc1 = JDocument.Load("simpson_ascii.json", new ASCIIEncoding());

            JDocument doc2 = JDocument.Load("simpson_utf8.json", new UTF8Encoding());
            JDocument doc3 = JDocument.Load("simpson_utf16LE.json", new UnicodeEncoding());
            //JDocument doc31 = JDocument.Load("simpson_utf16BE.json", new UnicodeEncoding()); // fails as expected
            //JDocument doc32 = JDocument.Load("simpson_utf16BE.json", new UnicodeEncoding(false, true, true)); // fails as expected
            JDocument doc33 = JDocument.Load("simpson_utf16BE.json", new UnicodeEncoding(true, true, true));

            JDocument doc4 = JDocument.Load("simpson_ascii.json", new UTF8Encoding());  // works, this is expected though, since all ascii characters are also encoded over 8 bits. They are therefore properly recognized.
            //JDocument doc41 = JDocument.Load("simpson_ascii.json", new UnicodeEncoding());  // fails as expected
            //JDocument doc5 = JDocument.Load("simpson_utf8.json", new ASCIIEncoding()); // fails as expected
            //JDocument doc36 = JDocument.Load("simpson_utf16LE.json", new ASCIIEncoding()); // fails as expected
            //JDocument doc37 = JDocument.Load("simpson_utf16LE.json", new UTF8Encoding());  // fails as expected
        }

        private static void WriteJsonDataWithByteOrderMark(string filename, Encoding encoding)
        {
            // example of JDocument written only through the constructor:
            new JDocument(
                        new JObjectMember("D'oh",
                                 new JObject(
                                            new JObjectMember("First Name", new JString("Homer")),
                                            new JObjectMember("Family Name", new JString("Simpson")),
                                            new JObjectMember("Is Fat?", new JTrue()),
                                            new JObjectMember("Children",
                                                new JArray
                                                        (
                                                            new JString("Bart"),
                                                            new JString("Lisa"),
                                                            new JString("Maggie")
                                                          )
                                            )
                                        )
                         ),
                         new JObjectMember("never gets older",
                                    new JObject(
                                                new JObjectMember("First Name", new JString("Bart")),
                                                new JObjectMember("Family Name", new JString("Simpson"))
                                                )
                                            )
                                ).Save(filename, encoding, true);
        }

        private static void WriteReadTest_MustSucceed(string filename)
        {
            if (filename.Length == 0)
                return;

            JDocument docW = WriteTest1_MustSucceed(filename);
            JDocument docR = ReadTest_MustSucceed(filename, false);

            //todo: compare docW and DocR
        }

        private static void ReadTest_mustFail(string filename)
        {
            if (filename.Length == 0)
                return;
            // the JSON data provided has to be voluntarily faulty and therefore allowed to check that an exception is properly thrown
            // I took test1.json, renamed it to testfail.json and modified it to make it faulty by removing tokens or adding wrong content for instance
            JDocument doc = JDocument.Load(filename);

        }

        private static JDocument ReadTest_MustSucceed(string filename, bool consoleOutput)
        {
            if (filename.Length == 0)
                return null;


            // first, load the document
            JDocument jDoc = JDocument.Load(filename);

            if (consoleOutput)
            {
                // then, you can use LINQ to parse the document
                var q = from o in jDoc.GetMembers()
                        select o;
                foreach (var o in q)
                {
                    // you can filter the values using their type
                    if (o.Value is JString || o.Value is JNumber)
                        Console.WriteLine("string or number: {0} : {1}", o.Name, o.Value.ToString(0));
                    else
                        if (o.Value is JFalse || o.Value is JTrue || o.Value is JNull)
                            Console.WriteLine("true, false or null: {0} : {1}", o.Name, o.Value.ToString(0));

                    // you can filter the values using their type 
                    if (o.Value is JObject)
                    {
                        JObject obj = o.Value as JObject;
                        Console.WriteLine("---(OBJECT)---\r\n\tobject name:{0} ({1} members)", o.Name, obj.Count);

                        Console.WriteLine("\t---All members (per member name, with type):");

                        var names = from name in obj.GetNames()
                                    select name;
                        foreach (var name in names)
                        {
                            // you can get an object with its name
                            IJValue value = obj[name];

                            if (value is JString || value is JNumber || value is JFalse || value is JTrue || value is JNull)
                                Console.WriteLine("\t{0} ({1}) : {2}", name, value.GetType().ToString(), value.ToString(0));
                            else
                                Console.WriteLine("\t{0} ({1})", name, value.GetType().ToString());
                        }

                        Console.WriteLine("\t---All values (without the member name):");
                        var values = from value in obj.GetValues()
                                     select value;
                        foreach (var value in values)
                        {
                            Console.WriteLine("\t{0}", value.ToString(1));
                        }

                        Console.WriteLine("\t---All members (with GetMembers()):");
                        var members = from member in obj.GetMembers()
                                      select member;
                        foreach (var member in members)
                        {
                            Console.WriteLine("\t{0} : {1}", member.Name, member.Value.ToString(0));
                        }
                    }

                    if (o.Value is JArray)
                    {
                        JArray jarray = o.Value as JArray;

                        Console.WriteLine("---(ARRAY)---\r\n\tarray name:{0} ({1} members)", o.Name, jarray.Count);

                        // you can use GetValues()
                        foreach (IJValue val in jarray.GetValues())
                        {
                            Console.WriteLine("\t-----element type {0}", val.GetType().ToString());
                        }

                        // ... or an index: jarray[i]
                        for (int i = 0; i < jarray.Count; i++)
                        {
                            Console.WriteLine("\t-----element type {0}", jarray[i].GetType().ToString());
                        }
                    }
                }
                Console.WriteLine();
                Console.WriteLine("Press ENTER to continue...");
                Console.ReadLine();
            }

            return jDoc;
        }

        private static void Writetest2_MustTriggerException(string filename)
        {
            if (filename.Length == 0)
                return;

            // all below should fail
            /**/
            // remove or put back the space between /** and the following / to switch the comments on / off

            JArray arrTest = new JArray(
                        new JNumber("+"), //fails as expected: we need a number
                        new JNumber("abcd"), //fails as expected: we need a number
                        new JNumber(""), //fails as expected: we need a number
                        new JNumber("123,654"), // fails as expected: the decimal separator is '.'
                        new JNumber("0xAB"), // fails as expected: no hexadecimal
                        new JNumber("0145"), // fails as expected: no octal
                        new JNumber("0123.123e456"), // fails as expected: if the number begins with 0, the next char must be '.'
                        new JNumber("+12"), // fails as expected: number begins with digit or -
                        new JNumber(".5"), // fails as expected: number begins with digit or -
                        new JNumber("6."), // fails as expected: decimal separator without fractional part
                        new JNumber("6,"), // fails as expected: decimal separator not '.' and fractional part missing
                        new JNumber("6 "), // fails as expected: no spaces after the number
                        new JNumber(" 6"), // fails as expected: number begins with digit or -
                        new JNumber("0x45") // fails as expected: no hexadecimal
                    );

            JDocument doc1 = new JDocument();
            doc1.Add("1", new JString(@"\a")); // fails as expected: \a is not allowed

            doc1.Add("0", new JString(@"\n\test\\""me:{}")); // fails as expected: \\"" will be read as 
            doc1.Add("1", new JNull());
            doc1.Add("1", new JString(@""));    //fails as expected: name "1" is used twice

            doc1.Add("2", new JString(@"\u1"));  //fails as expected: \u should be followed by four hexadecimal digits
            doc1.Add("3", new JString(@"\u123G"));  //fails as expected: \u should be followed by four hexadecimal digits
            //*/
        }

        private static JDocument WriteTest1_MustSucceed(string filename)
        {
            if (filename.Length == 0)
                return null;

            // you can create an empty object and add values
            JObject obj = new JObject();

            obj.Add("_number", new JNumber(-3.14));
            obj.Add("_true", new JTrue());   // notice the use of JTrue 
            obj.Add("_null", new JNull());   // notice the use of JNull

            // you can also add the values directly, thanks to JObjectMember
            JObject homer = new JObject(
                                new JObjectMember("First Name", new JString("Homer")),
                                new JObjectMember("Family Name", new JString("Simpson")),
                                new JObjectMember("Is Yellow?", new JTrue()), // notice the use of JTrue 
                                new JObjectMember("Is Blue?", new JFalse()) // notice the use of JFalse
                                );

            // you can create an empty array and add values
            JArray arr = new JArray();
            // ... either only one value
            arr.Add(new JNumber("-15.64"));

            // ... or more than one at once
            // Notice that prefixing your strings with @ will help keeping them be valid JSON strings
            arr.Add(new JString(@"Unicode: \u12A0"),
                    new JString(@"\n\test\""me:{}"),   // this string is valid because
                // 1. \test is seen as \t + est
                // 2. \"" will be checked as \"
                    new JString("\\n\\test\\\"me:{}"),  // same string, without the prefix @
                    new JString(@"\\test"),             // how to get the Json string \\test
                    new JString("\\\""),
                    new JString(@"\\"),
                    homer);                 // we added an object in the array :)

            // or you can add the value directly
            JArray otherArray = new JArray(
                        new JString("test2"),
                        new JString(string.Empty),
                        new JNull());           // notice the use of JNull
            // ... you can still add values 
            otherArray.Add(obj);

            // here some valid numbers
            JArray arrTest = new JArray(
                new JNumber("-0.3"),
                new JNumber("12"),
                new JNumber("0"),
                new JNumber("5"),
                new JNumber("12345"),
                new JNumber("3.45"),
                new JNumber("12.35"),
                new JNumber("54e-5"),
                new JNumber("12.54E+45"),
                new JNumber("1.5e-8"),
                new JNumber("12345.6789"),
                new JNumber("0.987"),
                new JNumber(4.16e-8)
                );

            // JDocument is a JObject itself, therefore we have the same two possibilities:
            // 1. add object members in the JDocument constructor

            JDocument doc = new JDocument(
                new JObjectMember("_false", new JFalse()),
                new JObjectMember("_false2", new JFalse())
                );   // the same name cannot be used twice!

            // 2. add object members with Add()

            // Add() has two forms:
            // 1. with JObjectMember
            // you can give one
            doc.Add(new JObjectMember("_Father Of the Year", homer));
            // or more
            doc.Add(
                new JObjectMember("_array", arr),
                new JObjectMember("_string1", new JString("string1")),
                new JObjectMember("_number2", new JNumber("-3.14"))
                );

            // 2. directly give the name and the value
            doc.Add("_otherArray", otherArray);
            doc.Add("_obj", obj);

            doc.Save(filename);

            return doc;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer IPG
Germany Germany
since 2010: C# with WPF
since 2002: C++ (MFC / QT)
since 1995: C, Java, Pascal


"if a drummer can do it, anobody can" - Bruce Dickinson

Comments and Discussions