2021-06-13 10:28:03 +02:00

1014 lines
23 KiB
C#

/* JSONObject.cs -- Simple C# JSON parser
version 1.4 - March 17, 2014
## changed by Stephan Hövelbrinks (stephan.hoevelbrinks@craftinglegends.com)
-- added InvariantCulture to string conversion
-- removed RegularExpressions System.Text.RegularExpressions from WinRT version
Copyright (C) 2012 Boomlagoon Ltd.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Boomlagoon Ltd.
contact@boomlagoon.com
*/
#if !UNITY_WINRT
#define PARSE_ESCAPED_UNICODE
#endif
#if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE || UNITY_WEBPLAYER || UNITY_WII || UNITY_PS3 || UNITY_XBOX360 || UNITY_FLASH
#define USE_UNITY_DEBUGGING
#endif
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
#if PARSE_ESCAPED_UNICODE
using System.Text.RegularExpressions;
#endif
#if USE_UNITY_DEBUGGING
using UnityEngine;
#else
using System.Diagnostics;
#endif
namespace AnimationImporter
{
namespace Boomlagoon.JSON
{
public static class Extensions
{
public static T Pop<T>(this List<T> list)
{
var result = list[list.Count - 1];
list.RemoveAt(list.Count - 1);
return result;
}
}
static class JSONLogger
{
#if USE_UNITY_DEBUGGING
public static void Log(string str)
{
Debug.Log(str);
}
public static void Error(string str)
{
Debug.LogError(str);
}
#else
public static void Log(string str) {
Debug.WriteLine(str);
}
public static void Error(string str) {
Debug.WriteLine(str);
}
#endif
}
public enum JSONValueType
{
String,
Number,
Object,
Array,
Boolean,
Null
}
public class JSONValue
{
public JSONValue(JSONValueType type)
{
Type = type;
}
public JSONValue(string str)
{
Type = JSONValueType.String;
Str = str;
}
public JSONValue(double number)
{
Type = JSONValueType.Number;
Number = number;
}
public JSONValue(JSONObject obj)
{
if (obj == null)
{
Type = JSONValueType.Null;
}
else {
Type = JSONValueType.Object;
Obj = obj;
}
}
public JSONValue(JSONArray array)
{
Type = JSONValueType.Array;
Array = array;
}
public JSONValue(bool boolean)
{
Type = JSONValueType.Boolean;
Boolean = boolean;
}
/// <summary>
/// Construct a copy of the JSONValue given as a parameter
/// </summary>
/// <param name="value"></param>
public JSONValue(JSONValue value)
{
Type = value.Type;
switch (Type)
{
case JSONValueType.String:
Str = value.Str;
break;
case JSONValueType.Boolean:
Boolean = value.Boolean;
break;
case JSONValueType.Number:
Number = value.Number;
break;
case JSONValueType.Object:
if (value.Obj != null)
{
Obj = new JSONObject(value.Obj);
}
break;
case JSONValueType.Array:
Array = new JSONArray(value.Array);
break;
}
}
public JSONValueType Type { get; private set; }
public string Str { get; set; }
public double Number { get; set; }
public JSONObject Obj { get; set; }
public JSONArray Array { get; set; }
public bool Boolean { get; set; }
public JSONValue Parent { get; set; }
public static implicit operator JSONValue(string str)
{
return new JSONValue(str);
}
public static implicit operator JSONValue(double number)
{
return new JSONValue(number);
}
public static implicit operator JSONValue(JSONObject obj)
{
return new JSONValue(obj);
}
public static implicit operator JSONValue(JSONArray array)
{
return new JSONValue(array);
}
public static implicit operator JSONValue(bool boolean)
{
return new JSONValue(boolean);
}
/// <returns>String representation of this JSONValue</returns>
public override string ToString()
{
switch (Type)
{
case JSONValueType.Object:
return Obj.ToString();
case JSONValueType.Array:
return Array.ToString();
case JSONValueType.Boolean:
return Boolean ? "true" : "false";
case JSONValueType.Number:
return Number.ToString(System.Globalization.CultureInfo.InvariantCulture);
case JSONValueType.String:
return "\"" + Str + "\"";
case JSONValueType.Null:
return "null";
}
return "null";
}
}
public class JSONArray : IEnumerable<JSONValue>
{
private readonly List<JSONValue> values = new List<JSONValue>();
public JSONArray()
{
}
/// <summary>
/// Construct a new array and copy each value from the given array into the new one
/// </summary>
/// <param name="array"></param>
public JSONArray(JSONArray array)
{
values = new List<JSONValue>();
foreach (var v in array.values)
{
values.Add(new JSONValue(v));
}
}
/// <summary>
/// Add a JSONValue to this array
/// </summary>
/// <param name="value"></param>
public void Add(JSONValue value)
{
values.Add(value);
}
public JSONValue this[int index]
{
get { return values[index]; }
set { values[index] = value; }
}
/// <returns>
/// Return the length of the array
/// </returns>
public int Length
{
get { return values.Count; }
}
/// <returns>String representation of this JSONArray</returns>
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.Append('[');
foreach (var value in values)
{
stringBuilder.Append(value.ToString());
stringBuilder.Append(',');
}
if (values.Count > 0)
{
stringBuilder.Remove(stringBuilder.Length - 1, 1);
}
stringBuilder.Append(']');
return stringBuilder.ToString();
}
public IEnumerator<JSONValue> GetEnumerator()
{
return values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return values.GetEnumerator();
}
/// <summary>
/// Attempt to parse a string as a JSON array.
/// </summary>
/// <param name="jsonString"></param>
/// <returns>A new JSONArray object if successful, null otherwise.</returns>
public static JSONArray Parse(string jsonString)
{
var tempObject = JSONObject.Parse("{ \"array\" :" + jsonString + '}');
return tempObject == null ? null : tempObject.GetValue("array").Array;
}
/// <summary>
/// Empty the array of all values.
/// </summary>
public void Clear()
{
values.Clear();
}
/// <summary>
/// Remove the value at the given index, if it exists.
/// </summary>
/// <param name="index"></param>
public void Remove(int index)
{
if (index >= 0 && index < values.Count)
{
values.RemoveAt(index);
}
else {
JSONLogger.Error("index out of range: " + index + " (Expected 0 <= index < " + values.Count + ")");
}
}
/// <summary>
/// Concatenate two JSONArrays
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns>A new JSONArray that is the result of adding all of the right-hand side array's values to the left-hand side array.</returns>
public static JSONArray operator +(JSONArray lhs, JSONArray rhs)
{
var result = new JSONArray(lhs);
foreach (var value in rhs.values)
{
result.Add(value);
}
return result;
}
}
public class JSONObject : IEnumerable<KeyValuePair<string, JSONValue>>
{
private enum JSONParsingState
{
Object,
Array,
EndObject,
EndArray,
Key,
Value,
KeyValueSeparator,
ValueSeparator,
String,
Number,
Boolean,
Null
}
private readonly IDictionary<string, JSONValue> values = new Dictionary<string, JSONValue>();
#if PARSE_ESCAPED_UNICODE
private static readonly Regex unicodeRegex = new Regex(@"\\u([0-9a-fA-F]{4})");
private static readonly byte[] unicodeBytes = new byte[2];
#endif
public JSONObject()
{
}
/// <summary>
/// Construct a copy of the given JSONObject.
/// </summary>
/// <param name="other"></param>
public JSONObject(JSONObject other)
{
values = new Dictionary<string, JSONValue>();
if (other != null)
{
foreach (var keyValuePair in other.values)
{
values[keyValuePair.Key] = new JSONValue(keyValuePair.Value);
}
}
}
/// <param name="key"></param>
/// <returns>Does 'key' exist in this object.</returns>
public bool ContainsKey(string key)
{
return values.ContainsKey(key);
}
public JSONValue GetValue(string key)
{
JSONValue value;
values.TryGetValue(key, out value);
return value;
}
public string GetString(string key)
{
var value = GetValue(key);
if (value == null)
{
JSONLogger.Error(key + "(string) == null");
return string.Empty;
}
return value.Str;
}
public double GetNumber(string key)
{
var value = GetValue(key);
if (value == null)
{
JSONLogger.Error(key + " == null");
return double.NaN;
}
return value.Number;
}
public JSONObject GetObject(string key)
{
var value = GetValue(key);
if (value == null)
{
JSONLogger.Error(key + " == null");
return null;
}
return value.Obj;
}
public bool GetBoolean(string key)
{
var value = GetValue(key);
if (value == null)
{
JSONLogger.Error(key + " == null");
return false;
}
return value.Boolean;
}
public JSONArray GetArray(string key)
{
var value = GetValue(key);
if (value == null)
{
JSONLogger.Error(key + " == null");
return null;
}
return value.Array;
}
public JSONValue this[string key]
{
get { return GetValue(key); }
set { values[key] = value; }
}
public void Add(string key, JSONValue value)
{
values[key] = value;
}
public void Add(KeyValuePair<string, JSONValue> pair)
{
values[pair.Key] = pair.Value;
}
/// <summary>
/// Attempt to parse a string into a JSONObject.
/// </summary>
/// <param name="jsonString"></param>
/// <returns>A new JSONObject or null if parsing fails.</returns>
public static JSONObject Parse(string jsonString)
{
if (string.IsNullOrEmpty(jsonString))
{
return null;
}
JSONValue currentValue = null;
var keyList = new List<string>();
var state = JSONParsingState.Object;
for (var startPosition = 0; startPosition < jsonString.Length; ++startPosition)
{
startPosition = SkipWhitespace(jsonString, startPosition);
switch (state)
{
case JSONParsingState.Object:
if (jsonString[startPosition] != '{')
{
return Fail('{', startPosition);
}
JSONValue newObj = new JSONObject();
if (currentValue != null)
{
newObj.Parent = currentValue;
}
currentValue = newObj;
state = JSONParsingState.Key;
break;
case JSONParsingState.EndObject:
if (jsonString[startPosition] != '}')
{
return Fail('}', startPosition);
}
if (currentValue.Parent == null)
{
return currentValue.Obj;
}
switch (currentValue.Parent.Type)
{
case JSONValueType.Object:
currentValue.Parent.Obj.values[keyList.Pop()] = new JSONValue(currentValue.Obj);
break;
case JSONValueType.Array:
currentValue.Parent.Array.Add(new JSONValue(currentValue.Obj));
break;
default:
return Fail("valid object", startPosition);
}
currentValue = currentValue.Parent;
state = JSONParsingState.ValueSeparator;
break;
case JSONParsingState.Key:
if (jsonString[startPosition] == '}')
{
--startPosition;
state = JSONParsingState.EndObject;
break;
}
var key = ParseString(jsonString, ref startPosition);
if (key == null)
{
return Fail("key string", startPosition);
}
keyList.Add(key);
state = JSONParsingState.KeyValueSeparator;
break;
case JSONParsingState.KeyValueSeparator:
if (jsonString[startPosition] != ':')
{
return Fail(':', startPosition);
}
state = JSONParsingState.Value;
break;
case JSONParsingState.ValueSeparator:
switch (jsonString[startPosition])
{
case ',':
state = currentValue.Type == JSONValueType.Object ? JSONParsingState.Key : JSONParsingState.Value;
break;
case '}':
state = JSONParsingState.EndObject;
--startPosition;
break;
case ']':
state = JSONParsingState.EndArray;
--startPosition;
break;
default:
return Fail(", } ]", startPosition);
}
break;
case JSONParsingState.Value:
{
var c = jsonString[startPosition];
if (c == '"')
{
state = JSONParsingState.String;
}
else if (char.IsDigit(c) || c == '-')
{
state = JSONParsingState.Number;
}
else
switch (c)
{
case '{':
state = JSONParsingState.Object;
break;
case '[':
state = JSONParsingState.Array;
break;
case ']':
if (currentValue.Type == JSONValueType.Array)
{
state = JSONParsingState.EndArray;
}
else {
return Fail("valid array", startPosition);
}
break;
case 'f':
case 't':
state = JSONParsingState.Boolean;
break;
case 'n':
state = JSONParsingState.Null;
break;
default:
return Fail("beginning of value", startPosition);
}
--startPosition; //To re-evaluate this char in the newly selected state
break;
}
case JSONParsingState.String:
var str = ParseString(jsonString, ref startPosition);
if (str == null)
{
return Fail("string value", startPosition);
}
switch (currentValue.Type)
{
case JSONValueType.Object:
currentValue.Obj.values[keyList.Pop()] = new JSONValue(str);
break;
case JSONValueType.Array:
currentValue.Array.Add(str);
break;
default:
JSONLogger.Error("Fatal error, current JSON value not valid");
return null;
}
state = JSONParsingState.ValueSeparator;
break;
case JSONParsingState.Number:
var number = ParseNumber(jsonString, ref startPosition);
if (double.IsNaN(number))
{
return Fail("valid number", startPosition);
}
switch (currentValue.Type)
{
case JSONValueType.Object:
currentValue.Obj.values[keyList.Pop()] = new JSONValue(number);
break;
case JSONValueType.Array:
currentValue.Array.Add(number);
break;
default:
JSONLogger.Error("Fatal error, current JSON value not valid");
return null;
}
state = JSONParsingState.ValueSeparator;
break;
case JSONParsingState.Boolean:
if (jsonString[startPosition] == 't')
{
if (jsonString.Length < startPosition + 4 ||
jsonString[startPosition + 1] != 'r' ||
jsonString[startPosition + 2] != 'u' ||
jsonString[startPosition + 3] != 'e')
{
return Fail("true", startPosition);
}
switch (currentValue.Type)
{
case JSONValueType.Object:
currentValue.Obj.values[keyList.Pop()] = new JSONValue(true);
break;
case JSONValueType.Array:
currentValue.Array.Add(new JSONValue(true));
break;
default:
JSONLogger.Error("Fatal error, current JSON value not valid");
return null;
}
startPosition += 3;
}
else {
if (jsonString.Length < startPosition + 5 ||
jsonString[startPosition + 1] != 'a' ||
jsonString[startPosition + 2] != 'l' ||
jsonString[startPosition + 3] != 's' ||
jsonString[startPosition + 4] != 'e')
{
return Fail("false", startPosition);
}
switch (currentValue.Type)
{
case JSONValueType.Object:
currentValue.Obj.values[keyList.Pop()] = new JSONValue(false);
break;
case JSONValueType.Array:
currentValue.Array.Add(new JSONValue(false));
break;
default:
JSONLogger.Error("Fatal error, current JSON value not valid");
return null;
}
startPosition += 4;
}
state = JSONParsingState.ValueSeparator;
break;
case JSONParsingState.Array:
if (jsonString[startPosition] != '[')
{
return Fail('[', startPosition);
}
JSONValue newArray = new JSONArray();
if (currentValue != null)
{
newArray.Parent = currentValue;
}
currentValue = newArray;
state = JSONParsingState.Value;
break;
case JSONParsingState.EndArray:
if (jsonString[startPosition] != ']')
{
return Fail(']', startPosition);
}
if (currentValue.Parent == null)
{
return currentValue.Obj;
}
switch (currentValue.Parent.Type)
{
case JSONValueType.Object:
currentValue.Parent.Obj.values[keyList.Pop()] = new JSONValue(currentValue.Array);
break;
case JSONValueType.Array:
currentValue.Parent.Array.Add(new JSONValue(currentValue.Array));
break;
default:
return Fail("valid object", startPosition);
}
currentValue = currentValue.Parent;
state = JSONParsingState.ValueSeparator;
break;
case JSONParsingState.Null:
if (jsonString[startPosition] == 'n')
{
if (jsonString.Length < startPosition + 4 ||
jsonString[startPosition + 1] != 'u' ||
jsonString[startPosition + 2] != 'l' ||
jsonString[startPosition + 3] != 'l')
{
return Fail("null", startPosition);
}
switch (currentValue.Type)
{
case JSONValueType.Object:
currentValue.Obj.values[keyList.Pop()] = new JSONValue(JSONValueType.Null);
break;
case JSONValueType.Array:
currentValue.Array.Add(new JSONValue(JSONValueType.Null));
break;
default:
JSONLogger.Error("Fatal error, current JSON value not valid");
return null;
}
startPosition += 3;
}
state = JSONParsingState.ValueSeparator;
break;
}
}
JSONLogger.Error("Unexpected end of string");
return null;
}
private static int SkipWhitespace(string str, int pos)
{
for (; pos < str.Length && char.IsWhiteSpace(str[pos]); ++pos) ;
return pos;
}
private static string ParseString(string str, ref int startPosition)
{
if (str[startPosition] != '"' || startPosition + 1 >= str.Length)
{
Fail('"', startPosition);
return null;
}
var endPosition = str.IndexOf('"', startPosition + 1);
if (endPosition <= startPosition)
{
Fail('"', startPosition + 1);
return null;
}
while (str[endPosition - 1] == '\\')
{
endPosition = str.IndexOf('"', endPosition + 1);
if (endPosition <= startPosition)
{
Fail('"', startPosition + 1);
return null;
}
}
var result = string.Empty;
if (endPosition > startPosition + 1)
{
result = str.Substring(startPosition + 1, endPosition - startPosition - 1);
}
startPosition = endPosition;
#if PARSE_ESCAPED_UNICODE
// Parse Unicode characters that are escaped as \uXXXX
do
{
Match m = unicodeRegex.Match(result);
if (!m.Success)
{
break;
}
string s = m.Groups[1].Captures[0].Value;
unicodeBytes[1] = byte.Parse(s.Substring(0, 2), NumberStyles.HexNumber);
unicodeBytes[0] = byte.Parse(s.Substring(2, 2), NumberStyles.HexNumber);
s = Encoding.Unicode.GetString(unicodeBytes);
result = result.Replace(m.Value, s);
} while (true);
#endif
return result;
}
private static double ParseNumber(string str, ref int startPosition)
{
if (startPosition >= str.Length || (!char.IsDigit(str[startPosition]) && str[startPosition] != '-'))
{
return double.NaN;
}
var endPosition = startPosition + 1;
for (;
endPosition < str.Length && str[endPosition] != ',' && str[endPosition] != ']' && str[endPosition] != '}';
++endPosition) ;
double result;
if (
!double.TryParse(str.Substring(startPosition, endPosition - startPosition), System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out result))
{
return double.NaN;
}
startPosition = endPosition - 1;
return result;
}
private static JSONObject Fail(char expected, int position)
{
return Fail(new string(expected, 1), position);
}
private static JSONObject Fail(string expected, int position)
{
JSONLogger.Error("Invalid json string, expecting " + expected + " at " + position);
return null;
}
/// <returns>String representation of this JSONObject</returns>
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.Append('{');
foreach (var pair in values)
{
stringBuilder.Append("\"" + pair.Key + "\"");
stringBuilder.Append(':');
stringBuilder.Append(pair.Value.ToString());
stringBuilder.Append(',');
}
if (values.Count > 0)
{
stringBuilder.Remove(stringBuilder.Length - 1, 1);
}
stringBuilder.Append('}');
return stringBuilder.ToString();
}
public IEnumerator<KeyValuePair<string, JSONValue>> GetEnumerator()
{
return values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return values.GetEnumerator();
}
/// <summary>
/// Empty this JSONObject of all values.
/// </summary>
public void Clear()
{
values.Clear();
}
/// <summary>
/// Remove the JSONValue attached to the given key.
/// </summary>
/// <param name="key"></param>
public void Remove(string key)
{
if (values.ContainsKey(key))
{
values.Remove(key);
}
}
}
}
}