Files
back_cantanBuilding/LocalPackages/rtm-sdk-unity-2.7.20/Runtime/Plugins/fpnn/common/JsonParser.cs
2026-05-26 16:15:54 +08:00

641 lines
19 KiB
C#

using System;
using System.Text;
using System.Collections.Generic;
namespace com.fpnn.common
{
public static class Json
{
public static object Parse(string json)
{
JsonParser parser = JsonParser.Create(json);
return parser.Parse();
}
public static Dictionary<string, object> ParseObject(string json)
{
JsonParser parser = JsonParser.Create(json);
return parser.ParseObject();
}
public static string ToString(object obj)
{
JsonStringify js = new JsonStringify();
return js.Stringify(obj);
}
}
//============================[ Exception ]============================//
public class JsonException : Exception
{
public JsonException(string message) : base(message) { }
public JsonException(String message, Exception ex) : base(message, ex) { }
}
public class InvalidJsonException : JsonException
{
public InvalidJsonException(string message) : base(message) { }
public InvalidJsonException(string message, Exception ex) : base(message, ex) { }
}
public class JsonTypeException : JsonException
{
public JsonTypeException(string message) : base(message) { }
}
//============================[ Json Parser ]============================//
internal enum JsonElementType
{
Empty,
Null,
Boolean,
String,
Int64,
UInt64,
Double,
Array,
Dictionary,
}
internal delegate void JsonParseSignDelegate(JsonParser parser);
internal class JsonParser
{
class JsonElement
{
public object element;
public JsonElementType type;
}
static private readonly Dictionary<char, JsonParseSignDelegate> signDelegateMap;
static private readonly Dictionary<char, ushort> hexTable;
static private readonly HashSet<char> numericalChars;
private readonly string json;
private int idx;
private string key;
private bool wantKey;
private bool wantValue;
private bool wantComma;
private bool wantSemicolon;
private readonly Stack<JsonElement> elementStack;
private object parseResult;
private JsonElementType resultType;
static JsonParser()
{
signDelegateMap = new Dictionary<char, JsonParseSignDelegate>
{
{ '{', EnterObject },
{ '}', ExitObject },
{ '[', EnterArray },
{ ']', ExitArray },
{ ',', ProcessComma },
{ ':', ProcessSemicolon },
{ '"', ProcessString },
};
hexTable = new Dictionary<char, ushort>
{
{ '0', 0 },
{ '1', 1 },
{ '2', 2 },
{ '3', 3 },
{ '4', 4 },
{ '5', 5 },
{ '6', 6 },
{ '7', 7 },
{ '8', 8 },
{ '9', 9 },
{ 'a', 10 },
{ 'b', 11 },
{ 'c', 12 },
{ 'd', 13 },
{ 'e', 14 },
{ 'f', 15 },
{ 'A', 10 },
{ 'B', 11 },
{ 'C', 12 },
{ 'D', 13 },
{ 'E', 14 },
{ 'F', 15 },
};
numericalChars = new HashSet<char>
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'+', '-', '.', 'E', 'e'
};
}
private static void EnterObject(JsonParser parser)
{
parser.EnterObject();
}
private static void ExitObject(JsonParser parser)
{
parser.ExitObject();
}
private static void EnterArray(JsonParser parser)
{
parser.EnterArray();
}
private static void ExitArray(JsonParser parser)
{
parser.ExitArray();
}
private static void ProcessComma(JsonParser parser)
{
parser.ProcessComma();
}
private static void ProcessSemicolon(JsonParser parser)
{
parser.ProcessSemicolon();
}
private static void ProcessString(JsonParser parser)
{
parser.ProcessString();
}
public static JsonParser Create(string json)
{
if (string.IsNullOrWhiteSpace(json))
throw new InvalidJsonException("Json Parser: content error. Null or empty content.");
JsonParser parser;
try
{
string normalizedString = json;
if (!json.IsNormalized())
normalizedString = json.Normalize();
parser = new JsonParser(normalizedString);
}
catch (ArgumentException e)
{
throw new InvalidJsonException("Json Parser: content error. Include invalid Unicode.", e);
}
return parser;
}
//---------------------[ Instance Parts ]--------------------------//
private JsonParser(string jsonString)
{
json = jsonString;
idx = 0;
elementStack = new Stack<JsonElement>();
}
private void Reset()
{
key = string.Empty;
wantKey = false;
wantValue = false;
wantComma = false;
wantSemicolon = false;
elementStack.Clear();
parseResult = null;
resultType = JsonElementType.Empty;
}
private void ParseCore()
{
Reset();
while (idx < json.Length && parseResult == null)
{
if (Char.IsWhiteSpace(json[idx]))
{
idx++;
continue;
}
if (signDelegateMap.TryGetValue(json[idx], out JsonParseSignDelegate method))
method(this);
else
GeneralProcess();
}
}
public object Parse()
{
ParseCore();
return parseResult;
}
public Dictionary<string, object> ParseObject()
{
ParseCore();
if (resultType == JsonElementType.Dictionary)
return (Dictionary<string, object>)parseResult;
throw new JsonTypeException("Json Parser: want the Dictionary type, but the parsed type is " + resultType.ToString("G") + ".");
}
private void FillKey(string value)
{
key = value;
wantKey = false;
wantValue = false;
wantComma = false;
wantSemicolon = true;
}
private void FillValue(object obj, JsonElementType type)
{
Dictionary<string, object> dict = (Dictionary<string, object>)elementStack.Peek().element;
try
{
dict.Add(key, obj);
}
catch (ArgumentException e)
{
throw new InvalidJsonException("Json Parser: reduplicated key: \"" + key + "\", offset " + idx, e);
}
wantKey = false;
wantValue = false;
wantComma = true;
wantSemicolon = false;
}
private void InsertNode(object obj, JsonElementType type)
{
int stackCount = elementStack.Count;
if (stackCount > 0)
{
if (wantValue)
FillValue(obj, type);
else
{
List<object> list = (List<object>)elementStack.Peek().element;
list.Add(obj);
wantComma = true;
}
}
if (type == JsonElementType.Array || type == JsonElementType.Dictionary)
{
elementStack.Push(new JsonElement()
{
element = obj,
type = type
});
}
else if (stackCount == 0)
{
parseResult = obj;
resultType = type;
}
}
private void FinishNode()
{
JsonElement element = elementStack.Pop();
if (elementStack.Count == 0)
{
parseResult = element.element;
resultType = element.type;
}
else
{
wantKey = false;
wantValue = false;
wantComma = true;
wantSemicolon = false;
}
}
private void ProcessSlash(ref StringBuilder stringBuilder)
{
idx++;
if (idx >= json.Length)
throw new InvalidJsonException("Json Parser: content error, json truncated after '\\'.");
switch (json[idx])
{
case '"':
stringBuilder.Append('"');
break;
case '\\':
stringBuilder.Append('\\');
break;
case '/':
stringBuilder.Append('/');
break;
case 'b':
stringBuilder.Append('\b');
break;
case 'f':
stringBuilder.Append('\f');
break;
case 'n':
stringBuilder.Append('\n');
break;
case 'r':
stringBuilder.Append('\r');
break;
case 't':
stringBuilder.Append('\t');
break;
case 'u':
if (idx + 5 >= json.Length)
throw new InvalidJsonException("Json Parser: content error, json truncated after '\\u'.");
idx++;
ushort value = 0;
for (int i = 0; i < 4; i++)
{
if (hexTable.TryGetValue(json[idx+i], out ushort v))
{
value <<= 4;
value += v;
}
else
throw new InvalidJsonException("Json Parser: content error, invalid hex number for '\\u'. Offset " + idx);
}
try
{
char c = Convert.ToChar(value);
stringBuilder.Append(c);
}
catch (OverflowException e)
{
throw new InvalidJsonException("Json Parser: content error, invalid unicode value for '\\u'. Offset " + idx, e);
}
idx += 4;
return;
}
idx++;
}
private string FetchString()
{
idx++;
int startIdx = idx;
StringBuilder stringBuilder = new StringBuilder();
while (idx < json.Length)
{
if (json[idx] == '\\')
{
int count = idx - startIdx;
if (count > 0)
stringBuilder.Append(json, startIdx, count);
ProcessSlash(ref stringBuilder);
startIdx = idx;
continue;
}
if (json[idx] == '"')
{
int count = idx - startIdx;
if (count > 0)
stringBuilder.Append(json, startIdx, count);
idx++;
return stringBuilder.ToString();
}
idx++;
}
throw new InvalidJsonException("Json Parser: content error, json truncated in string value.");
}
private void EnterObject()
{
if (wantKey || wantSemicolon || wantComma)
throw new InvalidJsonException("Json Parser: content error, '{' at improper place. Offset " + idx);
InsertNode(new Dictionary<string, object>(), JsonElementType.Dictionary);
wantKey = true;
wantValue = false;
wantComma = false;
wantSemicolon = false;
idx++;
}
private void ExitObject()
{
if (wantValue || wantSemicolon || elementStack.Count == 0 || elementStack.Peek().type != JsonElementType.Dictionary)
throw new InvalidJsonException("Json Parser: content error, '}' at improper place. Offset " + idx);
idx++;
FinishNode();
}
private void EnterArray()
{
if (wantKey || wantSemicolon || wantComma)
throw new InvalidJsonException("Json Parser: content error, '[' at improper place. Offset " + idx);
InsertNode(new List<object>(), JsonElementType.Array);
wantKey = false;
wantValue = false;
wantComma = false;
wantSemicolon = false;
idx++;
}
private void ExitArray()
{
if (wantValue || wantSemicolon || elementStack.Count == 0 || elementStack.Peek().type != JsonElementType.Array)
throw new InvalidJsonException("Json Parser: content error, ']' at improper place. Offset " + idx);
idx++;
FinishNode();
}
private void ProcessComma()
{
if (!wantComma)
throw new InvalidJsonException("Json Parser: content error. ',' at error position. Offset " + idx);
wantComma = false;
if (elementStack.Peek().type == JsonElementType.Dictionary)
wantKey = true;
idx++;
}
private void ProcessSemicolon()
{
if (!wantSemicolon)
throw new InvalidJsonException("Json Parser: content error. ':' at error position. Offset " + idx);
wantSemicolon = false;
wantValue = true;
idx++;
}
private void ProcessString()
{
if (wantSemicolon || wantComma)
throw new InvalidJsonException("Json Parser: content error. Require ':' or ','. Offset " + idx);
string value = FetchString();
if (wantKey)
FillKey(value);
else
InsertNode(value, JsonElementType.String);
}
private void GeneralProcess()
{
if (wantKey || wantSemicolon || wantComma)
throw new InvalidJsonException("Json Parser: content error. Require '\"' or ':' or ','. Offset " + idx);
if (idx + 3 < json.Length)
{
string tmp = json.Substring(idx, 4);
if (string.Equals(tmp, "true", StringComparison.OrdinalIgnoreCase))
{
idx += 4;
InsertNode(true, JsonElementType.Boolean);
return;
}
if (string.Equals(tmp, "null", StringComparison.OrdinalIgnoreCase))
{
idx += 4;
InsertNode(null, JsonElementType.Null);
return;
}
}
if (idx + 4 < json.Length)
{
string tmp = json.Substring(idx, 5);
if (string.Equals(tmp, "false", StringComparison.OrdinalIgnoreCase))
{
idx += 5;
InsertNode(false, JsonElementType.Boolean);
return;
}
}
ProcessNumber();
}
private void ProcessNumber()
{
int dot = 0;
int e = 0;
int startIndex = idx;
while (idx < json.Length && numericalChars.Contains(json[idx]))
{
if (json[idx] == '.')
dot++;
else if (json[idx] == 'e' || json[idx] == 'E')
e++;
idx++;
}
if (startIndex == idx)
{
if (idx + 7 < json.Length)
{
string tmp = json.Substring(idx, 8);
if (string.Equals(tmp, "Infinity", StringComparison.OrdinalIgnoreCase))
{
idx += 8;
InsertNode(Double.PositiveInfinity, JsonElementType.Double);
return;
}
}
if (idx + 2 < json.Length)
{
string tmp = json.Substring(idx, 3);
if (string.Equals(tmp, "nan", StringComparison.OrdinalIgnoreCase))
{
idx += 3;
InsertNode(Double.NaN, JsonElementType.Double);
return;
}
if (string.Equals(tmp, "inf", StringComparison.OrdinalIgnoreCase))
{
idx += 3;
InsertNode(Double.PositiveInfinity, JsonElementType.Double);
return;
}
}
throw new InvalidJsonException("Json Parser: content error. Unparseable content. Offset " + idx);
}
if (e > 1 || dot > 1)
throw new InvalidJsonException("Json Parser: content error. Unparseable content, invalid number. Offset " + idx);
string numberString = json.Substring(startIndex, idx - startIndex);
if (e > 0 || dot > 0)
{
try
{
double value = Convert.ToDouble(numberString);
InsertNode(value, JsonElementType.Double);
return;
}
catch (Exception ex)
{
throw new InvalidJsonException("Json Parser: content error. Invalid number. Offset " + idx, ex);
}
}
try
{
try
{
long value = Convert.ToInt64(numberString);
InsertNode(value, JsonElementType.Int64);
return;
}
catch (OverflowException)
{
UInt64 value = Convert.ToUInt64(numberString);
InsertNode(value, JsonElementType.UInt64);
}
}
catch (Exception ex)
{
throw new InvalidJsonException("Json Parser: content error. Invalid number. Offset " + idx, ex);
}
}
}
}