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