You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
11 KiB
C#
403 lines
11 KiB
C#
11 months ago
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Globalization;
|
||
|
using System.Text;
|
||
|
|
||
|
namespace JsonHelper
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// This class encodes and decodes JSON strings.
|
||
|
/// Spec. details, see http://www.json.org/
|
||
|
///
|
||
|
/// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
|
||
|
/// All numbers are parsed to doubles.
|
||
|
/// </summary>
|
||
|
internal class JsonParser
|
||
|
{
|
||
|
enum Token
|
||
|
{
|
||
|
None = -1, // Used to denote no Lookahead available
|
||
|
Curly_Open,
|
||
|
Curly_Close,
|
||
|
Squared_Open,
|
||
|
Squared_Close,
|
||
|
Colon,
|
||
|
Comma,
|
||
|
String,
|
||
|
Number,
|
||
|
True,
|
||
|
False,
|
||
|
Null
|
||
|
}
|
||
|
|
||
|
readonly char[] json;
|
||
|
readonly StringBuilder s = new StringBuilder();
|
||
|
Token lookAheadToken = Token.None;
|
||
|
int index;
|
||
|
|
||
|
internal JsonParser(string json)
|
||
|
{
|
||
|
this.json = json.ToCharArray();
|
||
|
}
|
||
|
|
||
|
public object Decode()
|
||
|
{
|
||
|
return ParseValue();
|
||
|
}
|
||
|
|
||
|
private Dictionary<string, object> ParseObject()
|
||
|
{
|
||
|
Dictionary<string, object> table = new Dictionary<string, object>();
|
||
|
|
||
|
ConsumeToken(); // {
|
||
|
|
||
|
while (true)
|
||
|
{
|
||
|
switch (LookAhead())
|
||
|
{
|
||
|
|
||
|
case Token.Comma:
|
||
|
ConsumeToken();
|
||
|
break;
|
||
|
|
||
|
case Token.Curly_Close:
|
||
|
ConsumeToken();
|
||
|
return table;
|
||
|
|
||
|
default:
|
||
|
{
|
||
|
|
||
|
// name
|
||
|
string name = ParseString();
|
||
|
|
||
|
// :
|
||
|
if (NextToken() != Token.Colon)
|
||
|
{
|
||
|
throw new Exception("Expected colon at index " + index);
|
||
|
}
|
||
|
|
||
|
// value
|
||
|
object value = ParseValue();
|
||
|
|
||
|
table[name] = value;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private ArrayList ParseArray()
|
||
|
{
|
||
|
ArrayList array = new ArrayList();
|
||
|
|
||
|
ConsumeToken(); // [
|
||
|
|
||
|
while (true)
|
||
|
{
|
||
|
switch (LookAhead())
|
||
|
{
|
||
|
|
||
|
case Token.Comma:
|
||
|
ConsumeToken();
|
||
|
break;
|
||
|
|
||
|
case Token.Squared_Close:
|
||
|
ConsumeToken();
|
||
|
return array;
|
||
|
|
||
|
default:
|
||
|
{
|
||
|
array.Add(ParseValue());
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private object ParseValue()
|
||
|
{
|
||
|
switch (LookAhead())
|
||
|
{
|
||
|
case Token.Number:
|
||
|
return ParseNumber();
|
||
|
|
||
|
case Token.String:
|
||
|
return ParseString();
|
||
|
|
||
|
case Token.Curly_Open:
|
||
|
return ParseObject();
|
||
|
|
||
|
case Token.Squared_Open:
|
||
|
return ParseArray();
|
||
|
|
||
|
case Token.True:
|
||
|
ConsumeToken();
|
||
|
return true;
|
||
|
|
||
|
case Token.False:
|
||
|
ConsumeToken();
|
||
|
return false;
|
||
|
|
||
|
case Token.Null:
|
||
|
ConsumeToken();
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
throw new Exception("Unrecognized token at index" + index);
|
||
|
}
|
||
|
|
||
|
private string ParseString()
|
||
|
{
|
||
|
ConsumeToken(); // "
|
||
|
|
||
|
s.Length = 0;
|
||
|
|
||
|
int runIndex = -1;
|
||
|
|
||
|
while (index < json.Length)
|
||
|
{
|
||
|
var c = json[index++];
|
||
|
|
||
|
if (c == '"')
|
||
|
{
|
||
|
if (runIndex != -1)
|
||
|
{
|
||
|
if (s.Length == 0)
|
||
|
return new string(json, runIndex, index - runIndex - 1);
|
||
|
|
||
|
s.Append(json, runIndex, index - runIndex - 1);
|
||
|
}
|
||
|
return s.ToString();
|
||
|
}
|
||
|
|
||
|
if (c != '\\')
|
||
|
{
|
||
|
if (runIndex == -1)
|
||
|
runIndex = index - 1;
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (index == json.Length) break;
|
||
|
|
||
|
if (runIndex != -1)
|
||
|
{
|
||
|
s.Append(json, runIndex, index - runIndex - 1);
|
||
|
runIndex = -1;
|
||
|
}
|
||
|
|
||
|
switch (json[index++])
|
||
|
{
|
||
|
case '"':
|
||
|
s.Append('"');
|
||
|
break;
|
||
|
|
||
|
case '\\':
|
||
|
s.Append('\\');
|
||
|
break;
|
||
|
|
||
|
case '/':
|
||
|
s.Append('/');
|
||
|
break;
|
||
|
|
||
|
case 'b':
|
||
|
s.Append('\b');
|
||
|
break;
|
||
|
|
||
|
case 'f':
|
||
|
s.Append('\f');
|
||
|
break;
|
||
|
|
||
|
case 'n':
|
||
|
s.Append('\n');
|
||
|
break;
|
||
|
|
||
|
case 'r':
|
||
|
s.Append('\r');
|
||
|
break;
|
||
|
|
||
|
case 't':
|
||
|
s.Append('\t');
|
||
|
break;
|
||
|
|
||
|
case 'u':
|
||
|
{
|
||
|
int remainingLength = json.Length - index;
|
||
|
if (remainingLength < 4) break;
|
||
|
|
||
|
// parse the 32 bit hex into an integer codepoint
|
||
|
uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]);
|
||
|
s.Append((char)codePoint);
|
||
|
|
||
|
// skip 4 chars
|
||
|
index += 4;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
throw new Exception("Unexpectedly reached end of string");
|
||
|
}
|
||
|
|
||
|
private uint ParseSingleChar(char c1, uint multipliyer)
|
||
|
{
|
||
|
uint p1 = 0;
|
||
|
if (c1 >= '0' && c1 <= '9')
|
||
|
p1 = (uint)(c1 - '0') * multipliyer;
|
||
|
else if (c1 >= 'A' && c1 <= 'F')
|
||
|
p1 = (uint)((c1 - 'A') + 10) * multipliyer;
|
||
|
else if (c1 >= 'a' && c1 <= 'f')
|
||
|
p1 = (uint)((c1 - 'a') + 10) * multipliyer;
|
||
|
return p1;
|
||
|
}
|
||
|
|
||
|
private uint ParseUnicode(char c1, char c2, char c3, char c4)
|
||
|
{
|
||
|
uint p1 = ParseSingleChar(c1, 0x1000);
|
||
|
uint p2 = ParseSingleChar(c2, 0x100);
|
||
|
uint p3 = ParseSingleChar(c3, 0x10);
|
||
|
uint p4 = ParseSingleChar(c4, 1);
|
||
|
|
||
|
return p1 + p2 + p3 + p4;
|
||
|
}
|
||
|
|
||
|
private string ParseNumber()
|
||
|
{
|
||
|
ConsumeToken();
|
||
|
|
||
|
// Need to start back one place because the first digit is also a token and would have been consumed
|
||
|
var startIndex = index - 1;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
var c = json[index];
|
||
|
|
||
|
if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
|
||
|
{
|
||
|
if (++index == json.Length) throw new Exception("Unexpected end of string whilst parsing number");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
} while (true);
|
||
|
|
||
|
return new string(json, startIndex, index - startIndex);
|
||
|
}
|
||
|
|
||
|
private Token LookAhead()
|
||
|
{
|
||
|
if (lookAheadToken != Token.None) return lookAheadToken;
|
||
|
|
||
|
return lookAheadToken = NextTokenCore();
|
||
|
}
|
||
|
|
||
|
private void ConsumeToken()
|
||
|
{
|
||
|
lookAheadToken = Token.None;
|
||
|
}
|
||
|
|
||
|
private Token NextToken()
|
||
|
{
|
||
|
var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore();
|
||
|
|
||
|
lookAheadToken = Token.None;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private Token NextTokenCore()
|
||
|
{
|
||
|
char c;
|
||
|
|
||
|
// Skip past whitespace
|
||
|
do
|
||
|
{
|
||
|
c = json[index];
|
||
|
|
||
|
if (c > ' ') break;
|
||
|
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;
|
||
|
|
||
|
} while (++index < json.Length);
|
||
|
|
||
|
if (index == json.Length)
|
||
|
{
|
||
|
throw new Exception("Reached end of string unexpectedly");
|
||
|
}
|
||
|
|
||
|
c = json[index];
|
||
|
|
||
|
index++;
|
||
|
|
||
|
//if (c >= '0' && c <= '9')
|
||
|
// return Token.Number;
|
||
|
|
||
|
switch (c)
|
||
|
{
|
||
|
case '{':
|
||
|
return Token.Curly_Open;
|
||
|
|
||
|
case '}':
|
||
|
return Token.Curly_Close;
|
||
|
|
||
|
case '[':
|
||
|
return Token.Squared_Open;
|
||
|
|
||
|
case ']':
|
||
|
return Token.Squared_Close;
|
||
|
|
||
|
case ',':
|
||
|
return Token.Comma;
|
||
|
|
||
|
case '"':
|
||
|
return Token.String;
|
||
|
|
||
|
case '0': case '1': case '2': case '3': case '4':
|
||
|
case '5': case '6': case '7': case '8': case '9':
|
||
|
case '-':
|
||
|
return Token.Number;
|
||
|
|
||
|
case ':':
|
||
|
return Token.Colon;
|
||
|
|
||
|
case 'f':
|
||
|
if (json.Length - index >= 4 &&
|
||
|
json[index + 0] == 'a' &&
|
||
|
json[index + 1] == 'l' &&
|
||
|
json[index + 2] == 's' &&
|
||
|
json[index + 3] == 'e')
|
||
|
{
|
||
|
index += 4;
|
||
|
return Token.False;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 't':
|
||
|
if (json.Length - index >= 3 &&
|
||
|
json[index + 0] == 'r' &&
|
||
|
json[index + 1] == 'u' &&
|
||
|
json[index + 2] == 'e')
|
||
|
{
|
||
|
index += 3;
|
||
|
return Token.True;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'n':
|
||
|
if (json.Length - index >= 3 &&
|
||
|
json[index + 0] == 'u' &&
|
||
|
json[index + 1] == 'l' &&
|
||
|
json[index + 2] == 'l')
|
||
|
{
|
||
|
index += 3;
|
||
|
return Token.Null;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
throw new Exception("Could not find token at index " + --index);
|
||
|
}
|
||
|
}
|
||
|
}
|