MiniJson面向物件封裝
阿新 • • 發佈:2018-11-03
MiniJson在unity上還是挺可以的,不過它生成的物件是Dictionary<string,object>和List<object>(舊版本生成Hashtable和ArrayList),對於C#這種強型別來說,使用還是不太方便,因此做了一個封裝可以直接生成指定物件或物件陣列。需要注意到的是,對於物件中的陣列欄位,只支援原始陣列,不支援ArrayList和List等。同時,object[]型別的陣列,因為它可以容納任意型別,而在json中並未記錄陣列元素具體是何種型別,因此,複雜元素會直接以Dictionary<string,object>或List<object>的形式寫入object[]中。
下面是擴充套件類:
namespace ND.CS.Json { using System.Collections; using System.Collections.Generic; using System; using System.Reflection; using Internal; /// <summary> /// 重新命名key和標記能否為Null /// </summary> public class JRAttribute : Attribute { public string Key; public bool Nullable; } /// <summary> /// MiniJson的面向物件擴充套件 /// </summary> public class UJson { public static string ToJson(object obj) { return Json.Serialize(ObjectSerialize(obj)); } public static string ToJson<T>(T[] array) { var list = new List<object>(array.Length); foreach (var i in array) { list.Add(i); } return Json.Serialize(ArraySerialize(list)); } public static T ToObject<T>(string j) where T:class, new() { var dict = Json.Deserialize(j) as Dictionary<string, object>; return ObjectDeserialize(typeof(T),dict) as T; } public static T[] ToObjectArray<T>(string j) { var jlist = Json.Deserialize(j) as List<object>; var list = (ArrayDeserialize(typeof(T),jlist) as IList); var array = new T[list.Count]; for (int i = 0; i < list.Count; i++) { array[i] = (T)list[i]; } return array; } /// <summary> /// minijson原始方法 /// </summary> /// <param name="json"></param> /// <returns></returns> public static object Deserialize(string json) { return Json.Deserialize(json); } /// <summary> /// minijson原始方法 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static string Serialize(object obj) { return Json.Serialize(obj); } private static Type TJObj = typeof(Dictionary<string, object>); private static Type TJArray = typeof(List<object>); private static bool IsStr(object obj) { return obj is string; } private static bool IsNum(object obj) { if (obj!=null) { return obj is int || obj is uint || obj is long || obj is sbyte || obj is byte || obj is short || obj is ushort || obj is ulong || obj is float || obj is double; } return false; } private static bool IsSimpleType(object obj) { return IsStr(obj) || IsNum(obj) || obj is bool; } private static bool IsJObject(object obj) { if (obj != null) { var t = obj.GetType(); return t == TJObj; } return false; } private static bool IsJArray(object obj) { if (obj != null) { var t = obj.GetType(); return t == TJArray; } return false; } public static Dictionary<string,object> ObjectSerialize(object obj) { if (obj == null) return null; var dict = new Dictionary<string, object>(); var fields = obj.GetType().GetFields(); foreach (FieldInfo i in fields) { var attrs = i.GetCustomAttributes(typeof(JRAttribute), true); var f = i.GetValue(obj); string key = i.Name; if (attrs != null && attrs.Length > 0) key = ((JRAttribute)attrs[0]).Key; if (IsSimpleType(f)) { dict.Add(key, i.GetValue(obj)); } else if (f is IList) { dict.Add(key, ArraySerialize(i.GetValue(obj))); } else { dict.Add(key, ObjectSerialize(i.GetValue(obj))); } } return dict; } public static List<object> ArraySerialize(object obj) { var list = obj as IList; var array = new List<object>(); foreach (object i in list) { if(IsSimpleType(i)) { array.Add(i); } else if (i is IList) { array.Add(ArraySerialize(i)); } else { array.Add(ObjectSerialize(i)); } } return array; } public static object ObjectDeserialize(Type t,Dictionary<string, object> dict) { if (dict == null) { return null; } var ctor = t.GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); var obj = ctor.Invoke(null); var fields = obj.GetType().GetFields(); foreach (FieldInfo i in fields) { var attrs = i.GetCustomAttributes(typeof(JRAttribute), true); string key = i.Name; bool nullable = false; if (attrs != null && attrs.Length > 0) { key = ((JRAttribute)attrs[0]).Key; nullable = ((JRAttribute)attrs[0]).Nullable; } var val = GetDictValue(dict, key,nullable); if (SetSimpleType(obj,i, val)) { } else if (i.FieldType.BaseType == typeof(Array)) { i.SetValue(obj, ArrayDeserialize(i.FieldType.GetElementType(), val as List<object>)); } else { i.SetValue(obj, ObjectDeserialize(i.FieldType, val as Dictionary<string, object>)); } } return obj; } /// <summary> /// object集合元素為多種型別時,轉到Json丟失元素型別資訊,因此對複合元素處理為List<object>和Dictionary<string, object> /// </summary> /// <param name="tUnit"></param> /// <param name="list"></param> /// <returns></returns> public static object ArrayDeserialize(Type tUnit, IList list) { if (list == null) { return null; } var obj = Array.CreateInstance(tUnit, list.Count); bool isObjectArray = tUnit == typeof(object); int idx = 0; foreach (object i in list) { if (i == null) { obj.SetValue(i, idx); } else if (IsSimpleType(i)) { if (i.GetType() == tUnit || isObjectArray) obj.SetValue(i, idx); else throw new InvalidCastException(string.Format("{0}不適用於{1}陣列", i.GetType(), tUnit)); } else if (i is IList) { if (i.GetType()==tUnit) obj.SetValue(ArrayDeserialize(tUnit,i as List<object>),idx); else if (isObjectArray) obj.SetValue(i as List<object>,idx); else throw new InvalidCastException(string.Format("{0}不適用於{1}陣列", i.GetType(), tUnit)); } else { if (isObjectArray) obj.SetValue(i as Dictionary<string, object>,idx); else obj.SetValue(ObjectDeserialize(tUnit, i as Dictionary<string, object>), idx); } idx++; } return obj; } private static long LongNum(object num) { if (num == null) { return 0; } Int64 v = (Int64)num; return v; } private static double DoubleNum(object num) { if (num == null) { return 0; } double v = (double)num; return v; } private static bool IsLongNum(object i) { if (i == null) { return false; } return i is int || i is uint || i is long || i is sbyte || i is byte || i is short || i is ushort || i is ulong; } private static bool SetSimpleType(object inst,FieldInfo field,object val) { bool ret = true; var f = field.GetValue(inst); object val2 = null; if (val == null) val = 0; else val2 = val; if (f is double || f is long || f is ulong) { field.SetValue(inst,val); } else if (f is float) { var v = (double)val; field.SetValue(inst, (float)v); } else if (f is decimal) { var v = (double)val; field.SetValue(inst, (decimal)v); } else if (f is int || f is uint) { var v = (long)val; field.SetValue(inst, (int)v); } else if (f is short || f is ushort) { var v = (long)val; field.SetValue(inst, (short)v); } else if (f is byte || f is sbyte) { var v = (long)val; field.SetValue(inst, (byte)v); } else if(field.FieldType == typeof(string)) { field.SetValue(inst, val2); } else if (field.FieldType == typeof(bool)) { if (val2 == null) val2 = false; field.SetValue(inst, val2); } else { ret = false; } return ret; } private static object GetDictValue(Dictionary<string,object> dict,string key,bool nullable) { try { return dict[key]; } catch (Exception) { if (nullable) { return null; } throw new Exception("不存在key:"+key); } } } }
當然,需要配合MiniJson使用,我把MiniJson放在一個名稱空間中了以免衝突:
/* * 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 ND.CS.Internal { // 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()); } } } } }
說下使用方法:
先定義資料型別,例如:
public class DEntry
{
public string dentry_id;
public string service_id;
public string parent_id;
public string path;
public int type;
[Obsolete("不建議使用")]
[JR(Nullable = true)]
public string inode_id;
public string name;
[JR(Nullable = true)]
public string other_name;
[JR(Nullable = true)]
public Dictionary<string, object> info;
public int scope;
[Obsolete("不建議使用")]
[JR(Nullable = true)]
public long uid;
[Obsolete("不建議使用")]
public int hits;
public long create_at;
public long update_at;
[Obsolete("不建議使用")]
public long expire_at;
public int flag;
[Obsolete("不建議使用")]
[JR(Nullable = true)]
public IndexNode inode;
[JR(Nullable = true)]
public UploadParams upload_params;
}
只支援公開欄位,不支援屬性,可以自己改一下。
注意到其中用了一個叫JR的特性,它有兩個欄位,設定了Key後,序列化和反序列化將以key作為json中的key,Nullable用來指示欄位可否為Null,預設為false,在反序列化時,如果json中缺少對應Key,將會丟擲異常,而指定Nullable為ture後,該欄位被設為型別預設值。
string UJson.ToJson(object obj) 把物件轉為json
string UJson.ToJson<T>(T[] array) 把物件陣列轉為json
T[] ToObjectArray<T>(string j) 把json轉為物件陣列
T ToObject<T>(string j) 把json轉為物件