627 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C#
		
	
	
	
		
		
			
		
	
	
			627 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C#
		
	
	
	
|  | /* | |||
|  |  * 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.Globalization; | |||
|  | 
 | |||
|  | namespace SRF | |||
|  | { | |||
|  |     using System; | |||
|  |     using System.Collections; | |||
|  |     using System.Collections.Generic; | |||
|  |     using System.IO; | |||
|  |     using System.Text; | |||
|  | 
 | |||
|  |     // 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); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <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); | |||
|  |         } | |||
|  | 
 | |||
|  |         private sealed class Parser : IDisposable | |||
|  |         { | |||
|  |             private const string WORD_BREAK = "{}[],:\""; | |||
|  |             private StringReader json; | |||
|  | 
 | |||
|  |             private Parser(string jsonString) | |||
|  |             { | |||
|  |                 json = new StringReader(jsonString); | |||
|  |             } | |||
|  | 
 | |||
|  |             private char PeekChar | |||
|  |             { | |||
|  |                 get { return Convert.ToChar(json.Peek()); } | |||
|  |             } | |||
|  | 
 | |||
|  |             private char NextChar | |||
|  |             { | |||
|  |                 get { return Convert.ToChar(json.Read()); } | |||
|  |             } | |||
|  | 
 | |||
|  |             private string NextWord | |||
|  |             { | |||
|  |                 get | |||
|  |                 { | |||
|  |                     var word = new StringBuilder(); | |||
|  | 
 | |||
|  |                     while (!IsWordBreak(PeekChar)) | |||
|  |                     { | |||
|  |                         word.Append(NextChar); | |||
|  | 
 | |||
|  |                         if (json.Peek() == -1) | |||
|  |                         { | |||
|  |                             break; | |||
|  |                         } | |||
|  |                     } | |||
|  | 
 | |||
|  |                     return word.ToString(); | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             private 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; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             public void Dispose() | |||
|  |             { | |||
|  |                 json.Dispose(); | |||
|  |                 json = null; | |||
|  |             } | |||
|  | 
 | |||
|  |             public static bool IsWordBreak(char c) | |||
|  |             { | |||
|  |                 return char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1; | |||
|  |             } | |||
|  | 
 | |||
|  |             public static object Parse(string jsonString) | |||
|  |             { | |||
|  |                 using (var instance = new Parser(jsonString)) | |||
|  |                 { | |||
|  |                     return instance.ParseValue(); | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             private Dictionary<string, object> ParseObject() | |||
|  |             { | |||
|  |                 var 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 | |||
|  |                             var name = ParseString(); | |||
|  |                             if (name == null) | |||
|  |                             { | |||
|  |                                 return null; | |||
|  |                             } | |||
|  | 
 | |||
|  |                             // : | |||
|  |                             if (NextToken != TOKEN.COLON) | |||
|  |                             { | |||
|  |                                 return null; | |||
|  |                             } | |||
|  |                             // ditch the colon | |||
|  |                             json.Read(); | |||
|  | 
 | |||
|  |                             // value | |||
|  |                             table[name] = ParseValue(); | |||
|  |                             break; | |||
|  |                     } | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             private List<object> ParseArray() | |||
|  |             { | |||
|  |                 var array = new List<object>(); | |||
|  | 
 | |||
|  |                 // ditch opening bracket | |||
|  |                 json.Read(); | |||
|  | 
 | |||
|  |                 // [ | |||
|  |                 var parsing = true; | |||
|  |                 while (parsing) | |||
|  |                 { | |||
|  |                     var nextToken = NextToken; | |||
|  | 
 | |||
|  |                     switch (nextToken) | |||
|  |                     { | |||
|  |                         case TOKEN.NONE: | |||
|  |                             return null; | |||
|  |                         case TOKEN.COMMA: | |||
|  |                             continue; | |||
|  |                         case TOKEN.SQUARED_CLOSE: | |||
|  |                             parsing = false; | |||
|  |                             break; | |||
|  |                         default: | |||
|  |                             var value = ParseByToken(nextToken); | |||
|  | 
 | |||
|  |                             array.Add(value); | |||
|  |                             break; | |||
|  |                     } | |||
|  |                 } | |||
|  | 
 | |||
|  |                 return array; | |||
|  |             } | |||
|  | 
 | |||
|  |             private object ParseValue() | |||
|  |             { | |||
|  |                 var nextToken = NextToken; | |||
|  |                 return ParseByToken(nextToken); | |||
|  |             } | |||
|  | 
 | |||
|  |             private 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; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             private string ParseString() | |||
|  |             { | |||
|  |                 var s = new StringBuilder(); | |||
|  |                 char c; | |||
|  | 
 | |||
|  |                 // ditch opening quote | |||
|  |                 json.Read(); | |||
|  | 
 | |||
|  |                 var 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 (var 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(); | |||
|  |             } | |||
|  | 
 | |||
|  |             private object ParseNumber() | |||
|  |             { | |||
|  |                 var number = NextWord; | |||
|  | 
 | |||
|  |                 if (number.IndexOf('.') == -1) | |||
|  |                 { | |||
|  |                     long parsedInt; | |||
|  |                     long.TryParse(number, out parsedInt); | |||
|  |                     return parsedInt; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 double parsedDouble; | |||
|  |                 double.TryParse(number, out parsedDouble); | |||
|  |                 return parsedDouble; | |||
|  |             } | |||
|  | 
 | |||
|  |             private void EatWhitespace() | |||
|  |             { | |||
|  |                 while (char.IsWhiteSpace(PeekChar)) | |||
|  |                 { | |||
|  |                     json.Read(); | |||
|  | 
 | |||
|  |                     if (json.Peek() == -1) | |||
|  |                     { | |||
|  |                         break; | |||
|  |                     } | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             private enum TOKEN | |||
|  |             { | |||
|  |                 NONE, | |||
|  |                 CURLY_OPEN, | |||
|  |                 CURLY_CLOSE, | |||
|  |                 SQUARED_OPEN, | |||
|  |                 SQUARED_CLOSE, | |||
|  |                 COLON, | |||
|  |                 COMMA, | |||
|  |                 STRING, | |||
|  |                 NUMBER, | |||
|  |                 TRUE, | |||
|  |                 FALSE, | |||
|  |                 NULL | |||
|  |             }; | |||
|  |         } | |||
|  | 
 | |||
|  |         private sealed class Serializer | |||
|  |         { | |||
|  |             private StringBuilder builder; | |||
|  | 
 | |||
|  |             private Serializer() | |||
|  |             { | |||
|  |                 builder = new StringBuilder(); | |||
|  |             } | |||
|  | 
 | |||
|  |             public static string Serialize(object obj) | |||
|  |             { | |||
|  |                 var instance = new Serializer(); | |||
|  | 
 | |||
|  |                 instance.SerializeValue(obj); | |||
|  | 
 | |||
|  |                 return instance.builder.ToString(); | |||
|  |             } | |||
|  | 
 | |||
|  |             private 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); | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             private void SerializeObject(IDictionary obj) | |||
|  |             { | |||
|  |                 var first = true; | |||
|  | 
 | |||
|  |                 builder.Append('{'); | |||
|  | 
 | |||
|  |                 foreach (var e in obj.Keys) | |||
|  |                 { | |||
|  |                     if (!first) | |||
|  |                     { | |||
|  |                         builder.Append(','); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     SerializeString(e.ToString()); | |||
|  |                     builder.Append(':'); | |||
|  | 
 | |||
|  |                     SerializeValue(obj[e]); | |||
|  | 
 | |||
|  |                     first = false; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 builder.Append('}'); | |||
|  |             } | |||
|  | 
 | |||
|  |             private void SerializeArray(IList anArray) | |||
|  |             { | |||
|  |                 builder.Append('['); | |||
|  | 
 | |||
|  |                 var first = true; | |||
|  | 
 | |||
|  |                 foreach (var obj in anArray) | |||
|  |                 { | |||
|  |                     if (!first) | |||
|  |                     { | |||
|  |                         builder.Append(','); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     SerializeValue(obj); | |||
|  | 
 | |||
|  |                     first = false; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 builder.Append(']'); | |||
|  |             } | |||
|  | 
 | |||
|  |             private void SerializeString(string str) | |||
|  |             { | |||
|  |                 builder.Append('\"'); | |||
|  | 
 | |||
|  |                 var 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: | |||
|  |                             var codepoint = Convert.ToInt32(c); | |||
|  |                             if ((codepoint >= 32) && (codepoint <= 126)) | |||
|  |                             { | |||
|  |                                 builder.Append(c); | |||
|  |                             } | |||
|  |                             else | |||
|  |                             { | |||
|  |                                 builder.Append("\\u"); | |||
|  |                                 builder.Append(codepoint.ToString("x4")); | |||
|  |                             } | |||
|  |                             break; | |||
|  |                     } | |||
|  |                 } | |||
|  | 
 | |||
|  |                 builder.Append('\"'); | |||
|  |             } | |||
|  | 
 | |||
|  |             private 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", CultureInfo.InvariantCulture)); | |||
|  |                 } | |||
|  |                 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", CultureInfo.InvariantCulture)); | |||
|  |                 } | |||
|  |                 else | |||
|  |                 { | |||
|  |                     SerializeString(value.ToString()); | |||
|  |                 } | |||
|  |             } | |||
|  |         } | |||
|  |     } | |||
|  | } |