From 6cbe64d47027d19a74d2be93d603e4fe06cd9f65 Mon Sep 17 00:00:00 2001 From: "scott.cgi" Date: Mon, 15 Jan 2018 22:02:04 +0800 Subject: [PATCH] v1.0 --- MojoUnityJson.cs | 931 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 931 insertions(+) create mode 100644 MojoUnityJson.cs diff --git a/MojoUnityJson.cs b/MojoUnityJson.cs new file mode 100644 index 0000000..ed52af8 --- /dev/null +++ b/MojoUnityJson.cs @@ -0,0 +1,931 @@ +/* +------------------------------------------------------------------------------------------------------------------------ + * Copyright (c) 2018 scott.cgi All Rights Reserved. + * + * Since : 2017-9-6 + * Author : scott.cgi + * Version: 1.0 + * Update : 2018-1-15 +------------------------------------------------------------------------------------------------------------------------ + */ + +using System.Collections; +using System.Collections.Generic; +using System; +using System.Text; + +namespace MojoUnity +{ + public static class Json + { + private const int JsonObjectInitCapacity = 8; + private const int JsonArrayInitCapacity = 8; + + #region Parse Json API + + /// + /// Parse json string. + /// + public static JsonValue Parse(string json) + { + var data = new Data(json, 0); + return ParseValue(ref data); + } + + #endregion + + + #region Parse Json + + /// + /// Parse the JsonValue. + /// + private static JsonValue ParseValue(ref Data data) + { + SkipWhiteSpace(ref data); + var c = data.json[data.index]; + + switch (c) + { + case '{': + return ParseObject(ref data); + + case '[': + return ParseArray (ref data); + + case '"': + return ParseString(ref data); + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return ParseNumber(ref data); + + case 'f': + if + ( + data.json[data.index + 1] == 'a' && + data.json[data.index + 2] == 'l' && + data.json[data.index + 3] == 's' && + data.json[data.index + 4] == 'e' + ) + { + data.index += 5; + return new JsonValue(JsonType.Bool, false); + } + break; + + case 't': + if + ( + data.json[data.index + 1] == 'r' && + data.json[data.index + 2] == 'u' && + data.json[data.index + 3] == 'e' + ) + { + data.index += 4; + return new JsonValue(JsonType.Bool, true); + } + break; + + case 'n': + if + ( + data.json[data.index + 1] == 'u' && + data.json[data.index + 2] == 'l' && + data.json[data.index + 3] == 'l' + ) + { + data.index += 4; + return new JsonValue(JsonType.Null, null); + } + break; + } + + throw new Exception(string.Format("Json ParseValue error on char '{0}' index in '{1}' ", c, data.index)); + } + + + /// + /// Parse JsonObject. + /// + private static JsonValue ParseObject(ref Data data) + { + var jsonObject = new Dictionary(JsonObjectInitCapacity); + + // skip '{' + data.index++; + + do + { + SkipWhiteSpace(ref data); + + if (data.json[data.index] == '}') + { + break; + } + + DebugTool.Assert + ( + data.json[data.index] == '"', + "Json ParseObject error, char '{0}' should be '\"' ", + data.json[data.index] + ); + + + // skip '"' + data.index++; + + var start = data.index; + + while (true) + { + var c = data.json[data.index++]; + + switch (c) + { + case '"': + // check end '"' + break; + + case '\\': + // skip escaped quotes + data.index++; + continue; + + default: + continue; + } + + // already skip the end '"' + break; + } + + // get object key string + var key = data.json.Substring(start, data.index - start - 1); + + SkipWhiteSpace(ref data); + DebugTool.Assert + ( + data.json[data.index] == ':', + "Json ParseObject error, after key = {0}, char '{1}' should be ':' ", + key, + data.json[data.index] + ); + + // skip ':' + data.index++; + + // set JsonObject key and value + jsonObject.Add(key, ParseValue(ref data)); + + SkipWhiteSpace(ref data); + + if (data.json[data.index] == ',') + { + data.index++ ; + } + else + { + SkipWhiteSpace(ref data); + DebugTool.Assert + ( + data.json[data.index] == '}', + "Json ParseObject error, after key = {0}, char '{1}' should be '{2}' ", + key, + data.json[data.index], + '}' + ); + + break; + } + } + while (true); + + // skip '}' and return after '}' + data.index++; + + return new JsonValue(JsonType.Object, jsonObject); + } + + + /// + /// Parse JsonArray. + /// + private static JsonValue ParseArray(ref Data data) + { + var jsonArray = new List(JsonArrayInitCapacity); + + // skip '[' + data.index++; + + do + { + SkipWhiteSpace(ref data); + + if (data.json[data.index] == ']') + { + break; + } + + // add JsonArray item + jsonArray.Add(ParseValue(ref data)); + + SkipWhiteSpace(ref data); + + if (data.json[data.index] == ',') + { + data.index++; + } + else + { + SkipWhiteSpace(ref data); + DebugTool.Assert + ( + data.json[data.index] == ']', + "Json ParseArray error, char '{0}' should be ']' ", + data.json[data.index] + ); + break; + } + } + while (true); + + // skip ']' + data.index++; + + return new JsonValue(JsonType.Array, jsonArray); + } + + + /// + /// Parses the JsonString. + /// + private static JsonValue ParseString(ref Data data) + { + // skip '"' + data.index++; + + var start = data.index; + string str; + + while (true) + { + switch (data.json[data.index++]) + { + case '"': + // check end '"' + + if (data.sb.Length == 0) + { + str = data.json.Substring(start, data.index - start - 1); + } + else + { + str = data.sb.Append(data.json, start, data.index - start - 1).ToString(); + // clear for next string + data.sb.Length = 0; + } + break; + + case '\\': + { + // check escaped char + + var escapedIndex = data.index; + char c; + + switch (data.json[data.index++]) + { + case '"': + c = '"'; + break; + + case '\'': + c = '\''; + break; + + case '\\': + c = '\\'; + break; + + case '/': + c = '/'; + break; + + case 'n': + c = '\n'; + break; + + case 'r': + c = '\r'; + break; + + case 't': + c = '\t'; + break; + + case 'u': + c = GetUnicodeCodePoint + ( + data.json[data.index], + data.json[data.index + 1], + data.json[data.index + 2], + data.json[data.index + 3] + ); + + // skip code point + data.index += 4; + break; + + default: + // not support just add in pre string + continue; + } + + // add pre string and escaped char + data.sb.Append(data.json, start, escapedIndex - start - 1).Append(c); + + // update pre string start index + start = data.index; + continue; + } + + default: + continue; + } + + // already skip the end '"' + break; + } + + return new JsonValue(JsonType.String, str); + } + + + /// + /// Parses the JsonNumber. + /// + private static JsonValue ParseNumber(ref Data data) + { + var start = data.index; + + while (true) + { + switch (data.json[++data.index]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '+': + case '.': + case 'e': + case 'E': + continue; + } + + break; + } + + var strNum = data.json.Substring(start, data.index - start); + float num; + + if (float.TryParse(strNum, out num)) + { + return new JsonValue(JsonType.Number, num); + } + else + { + throw new Exception(string.Format("Json ParseNumber error, can not parse string [{0}]", strNum)); + } + } + + + /// + /// Skip the white space. + /// + private static void SkipWhiteSpace(ref Data data) + { + while (true) + { + switch (data.json[data.index]) + { + case ' ' : + case '\t': + case '\n': + case '\r': + data.index++; + continue; + } + + break; + } + } + + + /// + /// Get the unicode code point. + /// + private static char GetUnicodeCodePoint(char c1, char c2, char c3, char c4) + { + return (char) + ( + UnicodeCharToInt(c1) * 0x1000 + + UnicodeCharToInt(c2) * 0x100 + + UnicodeCharToInt(c3) * 0x10 + + UnicodeCharToInt(c4) + ); + } + + + /// + /// Single unicode char convert to int. + /// + private static int UnicodeCharToInt(char c) + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return c - '0'; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return c - 'a' + 10; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return c - 'A' + 10; + } + + throw new Exception(string.Format("Json Unicode char '{0}' error", c)); + } + + #endregion + + +//---------------------------------------------------------------------------------------------------------------------- + + + private struct Data + { + public string json; + public int index; + public StringBuilder sb; + + + public Data(string json, int index) + { + this.json = json; + this.index = index; + this.sb = new StringBuilder(); + } + } + + } + + +//---------------------------------------------------------------------------------------------------------------------- + + + public enum JsonType + { + Object, + Array, + String, + Number, + Bool, + Null, + } + + +//---------------------------------------------------------------------------------------------------------------------- + + + public class JsonValue + { + public readonly JsonType type; + private object objValue; + private float floatValue; + private bool boolValue; + + + public JsonValue(JsonType type, object objValue) + { + this.type = type; + this.objValue = objValue; + } + + + public JsonValue(JsonType type, float floatValue) + { + this.type = type; + this.floatValue = floatValue; + } + + + public JsonValue(JsonType type, bool boolValue) + { + this.type = type; + this.boolValue = boolValue; + } + + +//---------------------------------------------------------------------------------------------------------------------- + + + #region JsonObject API + + + /// + /// Use JsonValue as JsonObject. + /// + public Dictionary AsObject() + { + DebugTool.Assert(this.type == JsonType.Object, "JsonValue type is not Object !"); + return this.objValue as Dictionary; + } + + + /// + /// Use JsonValue as JsonObject and get JsonValue item by key. + /// return null if not found key. + /// + public JsonValue AsObjectGet(string key) + { + DebugTool.Assert(this.type == JsonType.Object, "JsonValue type is not Object !"); + var dict = this.objValue as Dictionary; + + JsonValue jsonValue; + + if (dict.TryGetValue(key, out jsonValue)) + { + return jsonValue; + } + else + { + return null; + } + } + + + /// + /// Use JsonValue as JsonObject and get JsonObject item by key. + /// return null if not found key. + /// + public Dictionary AsObjectGetObject(string key) + { + var jsonValue = this.AsObjectGet(key); + + if (jsonValue != null) + { + return jsonValue.AsObject(); + } + else + { + return null; + } + } + + + /// + /// Use JsonValue as JsonObject and get JsonArray item by key. + /// return null if not found key. + /// + public List AsObjectGetArray(string key) + { + var jsonValue = this.AsObjectGet(key); + + if (jsonValue != null) + { + return jsonValue.AsArray(); + } + else + { + return null; + } + } + + + /// + /// Use JsonValue as JsonObject and get string item by key. + /// return null if not found key. + /// + public string AsObjectGetString(string key) + { + var jsonValue = this.AsObjectGet(key); + + if (jsonValue != null) + { + return jsonValue.AsString(); + } + else + { + return null; + } + } + + + /// + /// Use JsonValue as JsonObject and get float item by key. + /// return defaultValue if not found key. + /// + public float AsObjectGetFloat(string key, float defaultValue) + { + var jsonValue = this.AsObjectGet(key); + + if (jsonValue != null) + { + return jsonValue.AsFloat(); + } + else + { + return defaultValue; + } + } + + + /// + /// Use JsonValue as JsonObject and get float item by key. + /// + public float AsObjectGetFloat(string key) + { + return this.AsObjectGet(key).AsFloat(); + } + + + /// + /// Use JsonValue as JsonObject and get int item by key. + /// return defaultValue if not found key. + /// + public int AsObjectGetInt(string key, int defaultValue) + { + var jsonValue = this.AsObjectGet(key); + + if (jsonValue != null) + { + return jsonValue.AsInt(); + } + else + { + return defaultValue; + } + } + + + /// + /// Use JsonValue as JsonObject and get int item by key. + /// + public int AsObjectGetInt(string key) + { + return this.AsObjectGet(key).AsInt(); + } + + + /// + /// Use JsonValue as JsonObject and get bool item by key. + /// return defaultValue if not found key. + /// + public bool AsObjectGetBool(string key, bool defaultValue) + { + var jsonValue = this.AsObjectGet(key); + + if (jsonValue != null) + { + return jsonValue.AsBool(); + } + else + { + return defaultValue; + } + } + + + /// + /// Use JsonValue as JsonObject and get int item by key. + /// + public bool AsObjectGetBool(string key) + { + return this.AsObjectGet(key).AsBool(); + } + + + /// + /// Use JsonValue as JsonObject and check null item by key. + /// + public bool AsObjectGetIsNull(string key) + { + var jsonValue = this.AsObjectGet(key); + + if (jsonValue != null) + { + return jsonValue.IsNull(); + } + else + { + return false; + } + } + + + #endregion + + +//---------------------------------------------------------------------------------------------------------------------- + + + #region JsonArray API + + + /// + /// Use JsonValue as JsonArray. + /// + public List AsArray() + { + DebugTool.Assert(this.type == JsonType.Array, "JsonValue type is not Array !"); + return this.objValue as List; + } + + + /// + /// Use JsonValue as JsonArray and get JsonValue item by index. + /// + public JsonValue AsArrayGet(int index) + { + DebugTool.Assert(this.type == JsonType.Array, "JsonValue type is not Array !"); + return (this.objValue as List)[index]; + } + + + /// + /// Use JsonValue as JsonArray and get JsonObject item by index. + /// + public Dictionary AsArrayGetObject(int index) + { + return this.AsArrayGet(index).AsObject(); + } + + + /// + /// Use JsonValue as JsonArray and get JsonArray item by index. + /// + public List AsArrayGetArray(int index) + { + return this.AsArrayGet(index).AsArray(); + } + + + /// + /// Use JsonValue as JsonArray and get string item by index. + /// + public string AsArrayGetString(int index) + { + return this.AsArrayGet(index).AsString(); + } + + + /// + /// Use JsonValue as JsonArray and get float item by index. + /// + public float AsArrayGetFloat(int index) + { + return this.AsArrayGet(index).AsFloat(); + } + + + /// + /// Use JsonValue as JsonArray and get int item by index. + /// + public int AsArrayGetInt(int index) + { + return this.AsArrayGet(index).AsInt(); + } + + + /// + /// Use JsonValue as JsonArray and get bool item by index. + /// + public bool AsArrayGetBool(int index) + { + return this.AsArrayGet(index).AsBool(); + } + + + /// + /// Use JsonValue as JsonArray and check null item by index. + /// + public bool AsArrayGetIsNull(int index) + { + return this.AsArrayGet(index).IsNull(); + } + + + #endregion + + +//---------------------------------------------------------------------------------------------------------------------- + + + #region Other Json value API + + + /// + /// Get JsonValue as string. + /// + public string AsString() + { + DebugTool.Assert(this.type == JsonType.String, "JsonValue type is not String !"); + return this.objValue as string; + } + + + /// + /// Get JsonValue as float. + /// + public float AsFloat() + { + DebugTool.Assert(this.type == JsonType.Number, "JsonValue type is not Number !"); + return this.floatValue; + } + + + /// + /// Get JsonValue as int. + /// + public int AsInt() + { + DebugTool.Assert(this.type == JsonType.Number, "JsonValue type is not Number !"); + return (int) this.floatValue; + } + + + /// + /// Get JsonValue as bool. + /// + public bool AsBool() + { + DebugTool.Assert(this.type == JsonType.Bool, "JsonValue type is not Bool !"); + return this.boolValue; + } + + + /// + /// Whether JsonValue is null ? + /// + public bool IsNull() + { + return this.type == JsonType.Null; + } + + + #endregion + } + + +//---------------------------------------------------------------------------------------------------------------------- + + + internal static class DebugTool + { + public static void Assert(bool condition, string msg, params object[] args) + { + if (condition == false) + { + throw new Exception(string.Format(msg, args)); + } + } + } +} +