|
|
@@ -0,0 +1,547 @@ |
|
|
|
/* |
|
|
|
* Copyright (c) 2013 Calvin Rien |
|
|
|
* |
|
|
|
* Based on the JSON parser by Patrick van Bergen |
|
|
|
* http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html |
|
|
|
* |
|
|
|
* Simplified it so that it doesn't throw exceptions |
|
|
|
* and can be used in Unity iPhone with maximum code stripping. |
|
|
|
* |
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining |
|
|
|
* a copy of this software and associated documentation files (the |
|
|
|
* "Software"), to deal in the Software without restriction, including |
|
|
|
* without limitation the rights to use, copy, modify, merge, publish, |
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to |
|
|
|
* permit persons to whom the Software is furnished to do so, subject to |
|
|
|
* the following conditions: |
|
|
|
* |
|
|
|
* The above copyright notice and this permission notice shall be |
|
|
|
* included in all copies or substantial portions of the Software. |
|
|
|
* |
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
*/ |
|
|
|
using System; |
|
|
|
using System.Collections; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.IO; |
|
|
|
using System.Text; |
|
|
|
|
|
|
|
namespace MiniJSON { |
|
|
|
// Example usage: |
|
|
|
// |
|
|
|
// using UnityEngine; |
|
|
|
// using System.Collections; |
|
|
|
// using System.Collections.Generic; |
|
|
|
// using MiniJSON; |
|
|
|
// |
|
|
|
// public class MiniJSONTest : MonoBehaviour { |
|
|
|
// void Start () { |
|
|
|
// var jsonString = "{ \"array\": [1.44,2,3], " + |
|
|
|
// "\"object\": {\"key1\":\"value1\", \"key2\":256}, " + |
|
|
|
// "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " + |
|
|
|
// "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " + |
|
|
|
// "\"int\": 65536, " + |
|
|
|
// "\"float\": 3.1415926, " + |
|
|
|
// "\"bool\": true, " + |
|
|
|
// "\"null\": null }"; |
|
|
|
// |
|
|
|
// var dict = Json.Deserialize(jsonString) as Dictionary<string,object>; |
|
|
|
// |
|
|
|
// Debug.Log("deserialized: " + dict.GetType()); |
|
|
|
// Debug.Log("dict['array'][0]: " + ((List<object>) dict["array"])[0]); |
|
|
|
// Debug.Log("dict['string']: " + (string) dict["string"]); |
|
|
|
// Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles |
|
|
|
// Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs |
|
|
|
// Debug.Log("dict['unicode']: " + (string) dict["unicode"]); |
|
|
|
// |
|
|
|
// var str = Json.Serialize(dict); |
|
|
|
// |
|
|
|
// Debug.Log("serialized: " + str); |
|
|
|
// } |
|
|
|
// } |
|
|
|
|
|
|
|
/// <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 IList and IDictionary. |
|
|
|
/// All numbers are parsed to doubles. |
|
|
|
/// </summary> |
|
|
|
public static class Json { |
|
|
|
/// <summary> |
|
|
|
/// Parses the string json into a value |
|
|
|
/// </summary> |
|
|
|
/// <param name="json">A JSON string.</param> |
|
|
|
/// <returns>An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false</returns> |
|
|
|
public static object Deserialize(string json) { |
|
|
|
// save the string for debug information |
|
|
|
if (json == null) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
return Parser.Parse(json); |
|
|
|
} |
|
|
|
|
|
|
|
sealed class Parser : IDisposable { |
|
|
|
const string WORD_BREAK = "{}[],:\""; |
|
|
|
|
|
|
|
public static bool IsWordBreak(char c) { |
|
|
|
return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1; |
|
|
|
} |
|
|
|
|
|
|
|
enum TOKEN { |
|
|
|
NONE, |
|
|
|
CURLY_OPEN, |
|
|
|
CURLY_CLOSE, |
|
|
|
SQUARED_OPEN, |
|
|
|
SQUARED_CLOSE, |
|
|
|
COLON, |
|
|
|
COMMA, |
|
|
|
STRING, |
|
|
|
NUMBER, |
|
|
|
TRUE, |
|
|
|
FALSE, |
|
|
|
NULL |
|
|
|
}; |
|
|
|
|
|
|
|
StringReader json; |
|
|
|
|
|
|
|
Parser(string jsonString) { |
|
|
|
json = new StringReader(jsonString); |
|
|
|
} |
|
|
|
|
|
|
|
public static object Parse(string jsonString) { |
|
|
|
using (var instance = new Parser(jsonString)) { |
|
|
|
return instance.ParseValue(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public void Dispose() { |
|
|
|
json.Dispose(); |
|
|
|
json = null; |
|
|
|
} |
|
|
|
|
|
|
|
Dictionary<string, object> ParseObject() { |
|
|
|
Dictionary<string, object> table = new Dictionary<string, object>(); |
|
|
|
|
|
|
|
// ditch opening brace |
|
|
|
json.Read(); |
|
|
|
|
|
|
|
// { |
|
|
|
while (true) { |
|
|
|
switch (NextToken) { |
|
|
|
case TOKEN.NONE: |
|
|
|
return null; |
|
|
|
case TOKEN.COMMA: |
|
|
|
continue; |
|
|
|
case TOKEN.CURLY_CLOSE: |
|
|
|
return table; |
|
|
|
default: |
|
|
|
// name |
|
|
|
string name = ParseString(); |
|
|
|
if (name == null) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// : |
|
|
|
if (NextToken != TOKEN.COLON) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
// ditch the colon |
|
|
|
json.Read(); |
|
|
|
|
|
|
|
// value |
|
|
|
table[name] = ParseValue(); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
List<object> ParseArray() { |
|
|
|
List<object> array = new List<object>(); |
|
|
|
|
|
|
|
// ditch opening bracket |
|
|
|
json.Read(); |
|
|
|
|
|
|
|
// [ |
|
|
|
var parsing = true; |
|
|
|
while (parsing) { |
|
|
|
TOKEN nextToken = NextToken; |
|
|
|
|
|
|
|
switch (nextToken) { |
|
|
|
case TOKEN.NONE: |
|
|
|
return null; |
|
|
|
case TOKEN.COMMA: |
|
|
|
continue; |
|
|
|
case TOKEN.SQUARED_CLOSE: |
|
|
|
parsing = false; |
|
|
|
break; |
|
|
|
default: |
|
|
|
object value = ParseByToken(nextToken); |
|
|
|
|
|
|
|
array.Add(value); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return array; |
|
|
|
} |
|
|
|
|
|
|
|
object ParseValue() { |
|
|
|
TOKEN nextToken = NextToken; |
|
|
|
return ParseByToken(nextToken); |
|
|
|
} |
|
|
|
|
|
|
|
object ParseByToken(TOKEN token) { |
|
|
|
switch (token) { |
|
|
|
case TOKEN.STRING: |
|
|
|
return ParseString(); |
|
|
|
case TOKEN.NUMBER: |
|
|
|
return ParseNumber(); |
|
|
|
case TOKEN.CURLY_OPEN: |
|
|
|
return ParseObject(); |
|
|
|
case TOKEN.SQUARED_OPEN: |
|
|
|
return ParseArray(); |
|
|
|
case TOKEN.TRUE: |
|
|
|
return true; |
|
|
|
case TOKEN.FALSE: |
|
|
|
return false; |
|
|
|
case TOKEN.NULL: |
|
|
|
return null; |
|
|
|
default: |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
string ParseString() { |
|
|
|
StringBuilder s = new StringBuilder(); |
|
|
|
char c; |
|
|
|
|
|
|
|
// ditch opening quote |
|
|
|
json.Read(); |
|
|
|
|
|
|
|
bool parsing = true; |
|
|
|
while (parsing) { |
|
|
|
|
|
|
|
if (json.Peek() == -1) { |
|
|
|
parsing = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
c = NextChar; |
|
|
|
switch (c) { |
|
|
|
case '"': |
|
|
|
parsing = false; |
|
|
|
break; |
|
|
|
case '\\': |
|
|
|
if (json.Peek() == -1) { |
|
|
|
parsing = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
c = NextChar; |
|
|
|
switch (c) { |
|
|
|
case '"': |
|
|
|
case '\\': |
|
|
|
case '/': |
|
|
|
s.Append(c); |
|
|
|
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': |
|
|
|
var hex = new char[4]; |
|
|
|
|
|
|
|
for (int i=0; i< 4; i++) { |
|
|
|
hex[i] = NextChar; |
|
|
|
} |
|
|
|
|
|
|
|
s.Append((char) Convert.ToInt32(new string(hex), 16)); |
|
|
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
default: |
|
|
|
s.Append(c); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return s.ToString(); |
|
|
|
} |
|
|
|
|
|
|
|
object ParseNumber() { |
|
|
|
string number = NextWord; |
|
|
|
|
|
|
|
if (number.IndexOf('.') == -1) { |
|
|
|
long parsedInt; |
|
|
|
Int64.TryParse(number, out parsedInt); |
|
|
|
return parsedInt; |
|
|
|
} |
|
|
|
|
|
|
|
double parsedDouble; |
|
|
|
Double.TryParse(number, out parsedDouble); |
|
|
|
return parsedDouble; |
|
|
|
} |
|
|
|
|
|
|
|
void EatWhitespace() { |
|
|
|
while (Char.IsWhiteSpace(PeekChar)) { |
|
|
|
json.Read(); |
|
|
|
|
|
|
|
if (json.Peek() == -1) { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
char PeekChar { |
|
|
|
get { |
|
|
|
return Convert.ToChar(json.Peek()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
char NextChar { |
|
|
|
get { |
|
|
|
return Convert.ToChar(json.Read()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
string NextWord { |
|
|
|
get { |
|
|
|
StringBuilder word = new StringBuilder(); |
|
|
|
|
|
|
|
while (!IsWordBreak(PeekChar)) { |
|
|
|
word.Append(NextChar); |
|
|
|
|
|
|
|
if (json.Peek() == -1) { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return word.ToString(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
TOKEN NextToken { |
|
|
|
get { |
|
|
|
EatWhitespace(); |
|
|
|
|
|
|
|
if (json.Peek() == -1) { |
|
|
|
return TOKEN.NONE; |
|
|
|
} |
|
|
|
|
|
|
|
switch (PeekChar) { |
|
|
|
case '{': |
|
|
|
return TOKEN.CURLY_OPEN; |
|
|
|
case '}': |
|
|
|
json.Read(); |
|
|
|
return TOKEN.CURLY_CLOSE; |
|
|
|
case '[': |
|
|
|
return TOKEN.SQUARED_OPEN; |
|
|
|
case ']': |
|
|
|
json.Read(); |
|
|
|
return TOKEN.SQUARED_CLOSE; |
|
|
|
case ',': |
|
|
|
json.Read(); |
|
|
|
return TOKEN.COMMA; |
|
|
|
case '"': |
|
|
|
return TOKEN.STRING; |
|
|
|
case ':': |
|
|
|
return TOKEN.COLON; |
|
|
|
case '0': |
|
|
|
case '1': |
|
|
|
case '2': |
|
|
|
case '3': |
|
|
|
case '4': |
|
|
|
case '5': |
|
|
|
case '6': |
|
|
|
case '7': |
|
|
|
case '8': |
|
|
|
case '9': |
|
|
|
case '-': |
|
|
|
return TOKEN.NUMBER; |
|
|
|
} |
|
|
|
|
|
|
|
switch (NextWord) { |
|
|
|
case "false": |
|
|
|
return TOKEN.FALSE; |
|
|
|
case "true": |
|
|
|
return TOKEN.TRUE; |
|
|
|
case "null": |
|
|
|
return TOKEN.NULL; |
|
|
|
} |
|
|
|
|
|
|
|
return TOKEN.NONE; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string |
|
|
|
/// </summary> |
|
|
|
/// <param name="json">A Dictionary<string, object> / List<object></param> |
|
|
|
/// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns> |
|
|
|
public static string Serialize(object obj) { |
|
|
|
return Serializer.Serialize(obj); |
|
|
|
} |
|
|
|
|
|
|
|
sealed class Serializer { |
|
|
|
StringBuilder builder; |
|
|
|
|
|
|
|
Serializer() { |
|
|
|
builder = new StringBuilder(); |
|
|
|
} |
|
|
|
|
|
|
|
public static string Serialize(object obj) { |
|
|
|
var instance = new Serializer(); |
|
|
|
|
|
|
|
instance.SerializeValue(obj); |
|
|
|
|
|
|
|
return instance.builder.ToString(); |
|
|
|
} |
|
|
|
|
|
|
|
void SerializeValue(object value) { |
|
|
|
IList asList; |
|
|
|
IDictionary asDict; |
|
|
|
string asStr; |
|
|
|
|
|
|
|
if (value == null) { |
|
|
|
builder.Append("null"); |
|
|
|
} else if ((asStr = value as string) != null) { |
|
|
|
SerializeString(asStr); |
|
|
|
} else if (value is bool) { |
|
|
|
builder.Append((bool) value ? "true" : "false"); |
|
|
|
} else if ((asList = value as IList) != null) { |
|
|
|
SerializeArray(asList); |
|
|
|
} else if ((asDict = value as IDictionary) != null) { |
|
|
|
SerializeObject(asDict); |
|
|
|
} else if (value is char) { |
|
|
|
SerializeString(new string((char) value, 1)); |
|
|
|
} else { |
|
|
|
SerializeOther(value); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void SerializeObject(IDictionary obj) { |
|
|
|
bool first = true; |
|
|
|
|
|
|
|
builder.Append('{'); |
|
|
|
|
|
|
|
foreach (object e in obj.Keys) { |
|
|
|
if (!first) { |
|
|
|
builder.Append(','); |
|
|
|
} |
|
|
|
|
|
|
|
SerializeString(e.ToString()); |
|
|
|
builder.Append(':'); |
|
|
|
|
|
|
|
SerializeValue(obj[e]); |
|
|
|
|
|
|
|
first = false; |
|
|
|
} |
|
|
|
|
|
|
|
builder.Append('}'); |
|
|
|
} |
|
|
|
|
|
|
|
void SerializeArray(IList anArray) { |
|
|
|
builder.Append('['); |
|
|
|
|
|
|
|
bool first = true; |
|
|
|
|
|
|
|
foreach (object obj in anArray) { |
|
|
|
if (!first) { |
|
|
|
builder.Append(','); |
|
|
|
} |
|
|
|
|
|
|
|
SerializeValue(obj); |
|
|
|
|
|
|
|
first = false; |
|
|
|
} |
|
|
|
|
|
|
|
builder.Append(']'); |
|
|
|
} |
|
|
|
|
|
|
|
void SerializeString(string str) { |
|
|
|
builder.Append('\"'); |
|
|
|
|
|
|
|
char[] charArray = str.ToCharArray(); |
|
|
|
foreach (var c in charArray) { |
|
|
|
switch (c) { |
|
|
|
case '"': |
|
|
|
builder.Append("\\\""); |
|
|
|
break; |
|
|
|
case '\\': |
|
|
|
builder.Append("\\\\"); |
|
|
|
break; |
|
|
|
case '\b': |
|
|
|
builder.Append("\\b"); |
|
|
|
break; |
|
|
|
case '\f': |
|
|
|
builder.Append("\\f"); |
|
|
|
break; |
|
|
|
case '\n': |
|
|
|
builder.Append("\\n"); |
|
|
|
break; |
|
|
|
case '\r': |
|
|
|
builder.Append("\\r"); |
|
|
|
break; |
|
|
|
case '\t': |
|
|
|
builder.Append("\\t"); |
|
|
|
break; |
|
|
|
default: |
|
|
|
int codepoint = Convert.ToInt32(c); |
|
|
|
if ((codepoint >= 32) && (codepoint <= 126)) { |
|
|
|
builder.Append(c); |
|
|
|
} else { |
|
|
|
builder.Append("\\u"); |
|
|
|
builder.Append(codepoint.ToString("x4")); |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
builder.Append('\"'); |
|
|
|
} |
|
|
|
|
|
|
|
void SerializeOther(object value) { |
|
|
|
// NOTE: decimals lose precision during serialization. |
|
|
|
// They always have, I'm just letting you know. |
|
|
|
// Previously floats and doubles lost precision too. |
|
|
|
if (value is float) { |
|
|
|
builder.Append(((float) value).ToString("R")); |
|
|
|
} else if (value is int |
|
|
|
|| value is uint |
|
|
|
|| value is long |
|
|
|
|| value is sbyte |
|
|
|
|| value is byte |
|
|
|
|| value is short |
|
|
|
|| value is ushort |
|
|
|
|| value is ulong) { |
|
|
|
builder.Append(value); |
|
|
|
} else if (value is double |
|
|
|
|| value is decimal) { |
|
|
|
builder.Append(Convert.ToDouble(value).ToString("R")); |
|
|
|
} else { |
|
|
|
SerializeString(value.ToString()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |