public static JsonValue Parse(string json)
{
JsonValue root = null;
JsonValue top = null;
string name = null;
var escapedNewlines = 0;
char[] jsonArray = null;
var i = 0;
for (; i < json.Length; i++)
{
var ch = json[i];
// skip white space
if (ch == '\x20' || ch == '\x9' || ch == '\xD' || ch == '\xA')
{
continue;
}
switch (ch)
{
case '{':
case '[':
{
// create new value
var value = new JsonValue {
Name = name
};
// name
name = null;
// type
value.Type = ch == '{' ? JsonType.Object : JsonType.Array;
// set top and root
if (top != null)
{
top.Append(value);
}
else if (root == null)
{
root = value;
}
else
{
throw new JsonParserException(i, "Second root. Only one root allowed");
}
top = value;
}
break;
case '}':
case ']':
{
if (top == null || top.Type != ((ch == '}') ? JsonType.Object : JsonType.Array))
{
throw new JsonParserException(i, "Mismatch closing brace/bracket");
}
// set top
top = top.Parent;
}
break;
case ':':
if (top == null || top.Type != JsonType.Object)
{
throw new JsonParserException(i, "Unexpected character");
}
break;
case ',':
CheckTop(top, i);
break;
case '"':
{
CheckTop(top, i);
// skip '"' character
i++;
ch = json[i];
var first = i;
var last = i;
var chLast = ch;
while (i < json.Length)
{
if (ch < '\x20')
{
throw new JsonParserException(first, "Control characters not allowed in strings");
}
if (ch == '\\')
{
switch (json[i + 1])
{
case '"':
chLast = '"';
break;
case '\\':
chLast = '\\';
break;
case '/':
chLast = '/';
break;
case 'b':
chLast = '\b';
break;
case 'f':
chLast = '\f';
break;
case 'n':
chLast = '\n';
++escapedNewlines;
break;
case 'r':
chLast = '\r';
break;
case 't':
chLast = '\t';
break;
case 'u':
{
if (jsonArray == null)
{
jsonArray = json.ToCharArray();
}
uint codepoint;
if (HexToInteger(json.Substring(i + 2, 4), out codepoint) != 4)
{
throw new JsonParserException(i, "Bad unicode codepoint");
}
if (codepoint <= 0x7F)
{
chLast = (char)codepoint;
}
else if (codepoint <= 0x7FF)
{
last++;
jsonArray[last] = (char)(0xC0 | (codepoint >> 6));
last++;
jsonArray[last] = (char)(0x80 | (codepoint & 0x3F));
}
else if (codepoint <= 0xFFFF)
{
last++;
jsonArray[last] = (char)(0xE0 | (codepoint >> 12));
last++;
jsonArray[last] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
last++;
jsonArray[last] = (char)(0x80 | (codepoint & 0x3F));
}
}
i += 4;
break;
default:
throw new JsonParserException(first, "Unrecognized escape sequence");
}
last++;
i++;
}
else if (ch == '"')
{
break;
}
else
{
last++;
i++;
ch = json[i];
}
}
if (top != null && (name == null && top.Type == JsonType.Object))
{
// field name in object
name = json.Substring(first, i - first);
}
else
{
// new string value
var value = new JsonValue {
Name = name
};
name = null;
value.Type = JsonType.String;
string s;
if (jsonArray != null)
{
var slice = new char[i - first];
Array.Copy(jsonArray, first, slice, 0, i - first);
s = new string(slice);
}
else
{
s = json.Substring(first, i - first);
}
value.Value = s;
if (top != null)
{
top.Append(value);
}
}
}
break;
case 'n':
case 't':
case 'f':
{
CheckTop(top, i);
// new null/bool value
var value = new JsonValue {
Name = name
};
name = null;
// null
if (ch == 'n' && json[i + 1] == 'u' && json[i + 2] == 'l' && json[i + 3] == 'l')
{
value.Type = JsonType.Null;
i += 3;
}
// true
else if (ch == 't' && json[i + 1] == 'r' && json[i + 2] == 'u' && json[i + 3] == 'e')
{
value.Type = JsonType.Boolean;
value.Value = true;
i += 3;
}
// false
else if (ch == 'f' && json[i + 1] == 'a' && json[i + 2] == 'l' && json[i + 3] == 's' && json[i + 4] == 'e')
{
value.Type = JsonType.Boolean;
value.Value = false;
i += 4;
}
else
{
throw new JsonParserException(i, "Unknown identifier");
}
if (top != null)
{
top.Append(value);
}
}
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
CheckTop(top, i);
// new number value
var value = new JsonValue {
Name = name
};
name = null;
value.Type = JsonType.Integer;
int first = i;
while (ch != '\x20' && ch != '\x9' && ch != '\xD' && ch != '\xA' && ch != ',' && ch != ']' && ch != '}')
{
if (ch == '.' || ch == 'e' || ch == 'E')
{
value.Type = JsonType.Decimal;
}
i++;
ch = json[i];
}
if (value.Type == JsonType.Integer)
{
if (TextToInteger(json.Substring(first, i - first), out var n) != i - first)
{
throw new JsonParserException(first, "Bad integer number");
}
else
{
value.Value = n;
}
i--;
}
if (value.Type == JsonType.Decimal)
{
if (TextToDecimal(json.Substring(first, i - first), out var d) != i - first)
{
throw new JsonParserException(first, "Bad decimal number");
}
value.Value = d;
i--;
}
if (top != null)
{
top.Append(value);
}
}
break;
default:
throw new JsonParserException(i, "Unexpected character");
}
}
if (top != null)
{
throw new JsonParserException(i, "Not all objects/arrays have been properly closed");
}
return(root);
}