Click here to Skip to main content
15,891,905 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 81.4K   2K   79  
A small LinqToJSON library in C#, and how it works
using System;

namespace Ranslant.JSON.Linq
{
    // maybe I should use more regular expressions for the parsing...
    internal sealed class JParser
    {
        private string _jsonText;   // not static, to be thread safe.

        internal JDocument ParseDocument(string text)
        {
            JDocument jDoc = null;
            
            _jsonText = text;

            // First I had the following code:
            //   Document jDoc = (JDocument)ParseObject();
            // it compiled, but threw an InvalidCastException (see http://msdn.microsoft.com/en-us/library/ms173105.aspx and search for 'Giraffe')
            //      Indeed there is no equivalent to the C++ dynamic_cast in C#, and therefore you cannot
            // convert a base class into one of its derived classes.
            //      The only simple, clean and elegant solution I found is to copy the members of the object returned
            // by ParseObject() into the base of the JDocument object (contructor JDocument(JObject jObject)).
            // Besides, this solution allows to generally create a JDocument out of any JObject, which can also be useful :)
            //      Another solution would have been to encapsulate a JObject instance in JDocument. I found this
            // to be not as nice, in terms of usability, as the first solution. 
            //      A third solution would have been to use the CopyTo() method of JObject. This uses reflection and
            // is not guaranteed to work according to http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/f7362ba9-48cd-49eb-9c65-d91355a3daee
            //      A 4th solution would have been to derive JDocument from IJValue. But then I loose inheriting the
            // JObject methods and I would have needed to duplicate them in JDocument. This is not clean.

            
            JObject root = ParseObject();

            // this should not happen, but I'd better be prepared
            if (root != null)
                jDoc = new JDocument(root);

            if (_jsonText.Length > 0)
                throw new JsonParserException("not all text parsed!");

            return jDoc;
        }

        private JObject ParseObject()
        {
            _jsonText = _jsonText.ConsumeToken(JToken.ObjectStart);

            JObject jObject = new JObject();

            while(!_jsonText.StartsWith(JToken.ObjectEnd) && _jsonText.Length > 0)
            {
                JString name = ParseString();

                _jsonText = _jsonText.ConsumeToken(JToken.ObjectMemberPairSeparator);

                IJValue value = ParseValue();

                jObject.Add(name.Content, value);

                try
                {
                    _jsonText = _jsonText.ConsumeToken(JToken.ValuesSeparator);
                }
                catch(JsonParserException e)
                {
                    // if we're at the last element, then we expect not to find JToken.ValuesSeparator
                    // we need to clean up the string first
                    _jsonText = _jsonText.TrimStart(JExtensions.spaces);
                    // then check if there's a good reason why JToken.ValuesSeparator was not found.
                    // In this case the good reason is that we have an end delimiter
                    if (!_jsonText.StartsWith(JToken.ObjectEnd))
                        throw e;
                }
            }

            _jsonText = _jsonText.ConsumeToken(JToken.ObjectEnd);

            return jObject;
        }

        // Oooh, factory pattern :)
        private IJValue ParseValue()
        {
            if (_jsonText.StartsWith(JToken.True))
                return ParseTrue();

            if (_jsonText.StartsWith(JToken.False))
                return ParseFalse();

            if (_jsonText.StartsWith(JToken.Null))
                return ParseNull();

            if (_jsonText.StartsWith(JToken.StringDelimiter))
                return ParseString();

            if (_jsonText.StartsWith(JToken.ObjectStart))
                return ParseObject();

            if (_jsonText.StartsWith(JToken.ArrayStart))
                return ParseArray();

            // if nothing else was found then we expect a number.
            // And if no number is found then we report an issue.
            return ParseNumber();
        }

        private JNumber ParseNumber()
        {
            string content = _jsonText.Substring(0, _jsonText.IndexOfAny(new char[] {' ', JToken.ValuesSeparator[0], JToken.ArrayEnd[0], JToken.ObjectEnd[0]}, 0));

            try
            {
                JNumber jNumber = new JNumber(content);
                _jsonText = _jsonText.Remove(0, content.Length);
                return jNumber;
            }
            catch (JsonException e)
            {
                throw e;
            }
            catch (Exception)
            {
                throw new JsonParserException("number expected, not found");
            }
        }

        private JArray ParseArray()
        {
            _jsonText = _jsonText.ConsumeToken(JToken.ArrayStart);

            JArray jArray = new JArray();

            while (!_jsonText.StartsWith(JToken.ArrayEnd) && _jsonText.Length > 0)
            {
                IJValue value = ParseValue();

                jArray.Add(value);

                try
                {
                    _jsonText = _jsonText.ConsumeToken(JToken.ValuesSeparator);
                }
                catch (JsonParserException e)
                {
                    // if we're at the last element, then we expect not to find JToken.ValuesSeparator
                    // we need to clean up the string first
                    _jsonText = _jsonText.TrimStart(JExtensions.spaces);
                    // then check if there's a good reason why JToken.ValuesSeparator was not found.
                    // In this case the good reason is that we have an end delimiter
                    if (!_jsonText.StartsWith(JToken.ArrayEnd))
                        throw e;
                }
            }

            _jsonText = _jsonText.ConsumeToken(JToken.ArrayEnd);

            return jArray;
        }

        private JNull ParseNull()
        {
            _jsonText = _jsonText.ConsumeToken(JToken.Null);
            return new JNull();
        }

        private JFalse ParseFalse()
        {
            _jsonText = _jsonText.ConsumeToken(JToken.False);
            return new JFalse();
        }

        private JTrue ParseTrue()
        {
            _jsonText = _jsonText.ConsumeToken(JToken.True);
            return new JTrue();
        }

        private JString ParseString()
        {
            _jsonText = _jsonText.ConsumeToken(JToken.StringDelimiter);

            // we have to include the escape character '\"' in the string.
            bool stringFound = false;
            bool nextCharIsEscaped = false;
            int stringLength = 0;
            while (!stringFound && stringLength < _jsonText.Length)
            {
                // the main question is any way: we we find a '"', is it escaped or not. If yes, then the string is not finished.
                if (nextCharIsEscaped)
                {
                    // if the next character is escaped, then we know this is not the end of the string
                    nextCharIsEscaped = false;
                }
                else    // if (!nextCharIsEscaped)
                {
                    if (_jsonText[stringLength] == '\\')
                        nextCharIsEscaped = true;
                    else
                        if (_jsonText[stringLength] == '"')
                            stringFound = true;
                }

                ++stringLength;
            }

            // do not include the delimiter
            JString jString = new JString(_jsonText.Substring(0, stringLength - 1));

            _jsonText = _jsonText.Remove(0, stringLength);

            return jString;
        }

    }
}

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