1. 程式人生 > >C# 中物件與Json互轉的方法整理筆記

C# 中物件與Json互轉的方法整理筆記

前言

         原先一直做CS的開發,對Json瞭解不多,最近轉做BS後,才接觸到了Json。作為後臺與前端頁面資料互動的基礎,Json的地位非常重要,自然而然Json轉換方法的重要性也不言面喻。

最開始使用的是同事原來寫好的物件轉Json的方法,並沒有什麼問題,不過一次偶然的轉換,讓我發現這個方法的侷限性。當時我嘗試將一個包含List屬性的物件轉成Json,該物件的List屬性中有若干個物件,本以為轉出來的Json字串會帶著這個屬性,同時帶著List中的所有物件,可實際轉換的結果卻是該屬性的值——Object。考慮到後面可能也會類似的情況要處理,我打算重寫該方法一次性解決這個問題。不過粗略研究程式碼後卻有些犯難,這部分程式碼涉及到反射和特性的知識,而我對這部分知識還不夠了解,嘗試了簡單的改寫後發現實現難度有些大,所以放棄了改寫的想法。

既然改寫難度大,那麼是否可以使用C#自帶類庫中類和方法呢?抱著這個想法在網上搜到了使用System.Runtime.Serialization.Json名稱空間中DataContractJsonSerializer類轉換Json的方法。在測試中發現使用這種方法轉換存在兩個問題:第一,生成的Json字串中,會將自動化屬性包裹在<>k__BackingField的尖括號中,如屬性”CreateTime”,轉換後會變成”< CreateTime>k__BackingField”;第二,如果物件的屬性中有時間型別,則會將該屬性的值轉換成\/Date(1409752544567+0800)\/類的格式,該格式中的數字是與1970年1月1日的毫秒數之差。解決第一個問題需要使用正則表示式查詢替換,解決第二個問題不但需要正則表示式查找出那些數字,還要將其轉換成時間。實現起來也很麻煩。

於是我又開始在網上尋找其他的解決方法,又發現使用System.Web.Script.Serialization名稱空間下的JavaScriptSerializer也可以實現Json物件的互轉。而且在測試中還發現使用這種方法不會將自動化屬性被包裹在<>k__BackingField的尖括號中的問題,不過時間轉換的問題依然存在。

如果C#自帶類庫中的類和方法無法方便的實現Json物件的互轉,就只能求助於第三方類庫了。JsonNet或說是NewtonSoft很快便吸引了我的注意。在測試的時候還出現了個小插曲,我使用從官方地址下載的壓縮包中BIN目錄下的Net4.0版本的庫檔案,在程式中執行到JsonConvert.SerializeObject方法的時候提示查詢JsonConvert.cs檔案。後來我開啟源專案重新編譯生成庫檔案後就能正常呼叫執行了。在測試中發現使用JsonConvert.SerializeObject預設的方法轉換出來的Json字串中,時間格式雖然不是我想要的,也屬於格式化後的。接著繼續在網上搜索是否有控制時間格式化的方法,終於找到了使用名稱空間Newtonsoft.Json.Converters中的IsoDateTimeConverter類物件可以控制時間的格式化。另外測試了物件陣列轉Json和Json轉物件及轉物件陣列的方法後,確認JsonNet適合使用。

另外在整理JsonNet實現程式碼的時候,意外發現了LitJson庫,比較小巧,據說轉換時的效率比較高。簡單測試了一下發現它實現了時間預設格式的轉換,不過不提供詳細的控制,在這點上就不如JsonNet好。

個人感覺Json與物件的互轉的技術實現關鍵在於反射和特性,待到有時間研究到這部分內容時再來深入分析、比較和學習。

以下收錄了一些從網上找的解決物件與Json互轉的方法,其中也包括處理時間格式化的幾種方式,僅供備忘參考。

1    解析Json的幾種方法

1.1         準備資料

實體類:

usingSystem.Runtime.Serialization;

 

namespace WindowsFormsApplication1

{

    [DataContract]

    public class Person

    {

        [DataMember(Order= 0, IsRequired =true)]

        public string Name { get; set; }

 

        [DataMember(Order= 1)]

        public int Age { get; set; }

 

        [DataMember(Order= 2)]

        public bool Alive { get; set; }

 

        [DataMember(Order= 3)]

        public string[] FavoriteFilms { get;set; }

 

        [DataMember(Order= 4)]

        public Person Child { get;set; }

    }

}

定義:

        Action<object> log = o =>Console.WriteLine(o);

        Func<int,int, int> add = (x, y) => x + y;

        var p1 = newPerson

        {

            Age = 12,

            Alive = true,

            Name = "lj",

            FavoriteFilms = new[] {"Up","Avatar" }

        };

        var p2 = newPerson() { Age = 28, Name ="cy", Child = p1 };

1.2         使用DataContractJsonSerializer

幫助類:

    // usingSystem.Runtime.Serialization.Json;

    ///<summary>

    ///解析JSON,仿Javascript風格

    ///</summary>

    public static class JsonTool1

    {

        public static T parse<T>(stringjsonString)

        {

            using(var ms =new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))

            {

                return(T)newDataContractJsonSerializer(typeof(T)).ReadObject(ms);

            }

        }

        public static stringstringify(object jsonObject)

        {

            using(var ms =new MemoryStream())

            {

                newDataContractJsonSerializer(jsonObject.GetType()).WriteObject(ms,jsonObject);

                returnEncoding.UTF8.GetString(ms.ToArray());

            }

        }

    }

用法:

   // 序列化

   var jsonString = JSON.stringify(new[] { p1, p2 });

   log(jsonString == JSON.stringify(new List<Person>() { p1, p2}));   //true

   log(jsonString);

   // 反序列化,泛型集合

   JSON.parse<List<Person>>(jsonString);

    // 陣列轉換            

   JSON.parse<Person[]>(jsonString);

輸出:

[{"Name":"lj","Age":12,"Alive":true,"FavoriteFilms":["Up","Avatar"],"Child":null

},{"Name":"cy","Age":28,"Alive":false,"FavoriteFilms":null,"Child":{"Name":"lj",

"Age":12,"Alive":true,"FavoriteFilms":["Up","Avatar"],"Child":null}}]

1.3         使用JavaScriptSerializer

   // using System.Web.Script.Serialization;

   var jser    = newJavaScriptSerializer();

   var json    = jser.Serialize(newList<Person>() { p1, p2 });

   var persons = jser.Deserialize<List<Person>>(json);

1.4         使用Silverlight

   // using System.Json

   var css = "{ /"#header/" :{background:/"red/"}, layout : [5,4,1],color:/"cyan/"}";

   var style = JsonObject.Parse(css) as JsonObject;    

    (

   from s in style

   where s.Key == "color"

   select (string)s.Value

   ).First().ToString();    

   // "cyan"

   // 更多操作

   style["layout"][0] = 22;

   var hd = style["#header"];

   style["body>div+p"] = hd;

   style.Remove("#header");

   var bd = new JsonObject();

   bd["border"] = "1px solid cyan";

   style["body>div+p"]["#meta"] = bd;

   style.ToString();    

   //{"layout":[22,4,1],"color":"cyan","body>div+p":{"background":"red","#meta":{"border":"1pxsolid cyan"}}}

1.5         使用JSON.NET

   // using Newtonsoft.Json;

   var json = JsonConvert.SerializeObject(new[] { p1, p2 });

   var persons =JsonConvert.DeserializeObject<List<Person>>(json);

   var ja = JArray.Parse(jsonString);            

   log(ja);    //注意,格式化過的輸出

輸出:

[

  {

   "Name": "lj",

   "Age": 12,

   "Alive": true,

   "FavoriteFilms": [

     "Up",

     "Avatar"

   ],

   "Child": null

  },

  {

   "Name": "cy",

   "Age": 28,

   "Alive": false,

   "FavoriteFilms": null,

   "Child": {

     "Name": "lj",

     "Age": 12,

     "Alive": true,

     "FavoriteFilms": [

       "Up",

       "Avatar"

     ],

     "Child": null

    }

  }

]

LINQ:

   var ageCount = ja.Select(j => (int)j["Age"]).Aggregate(add);    

   var q = from j in ja

           where !j["Name"].Value<string>().Equals("lj")

           select (int)j["Age"];

   log(q.Aggregate(add) == ageCount); //false

其他:

   // 與Linq to XML 相似的巢狀建構函式:

   var jo = new JObject(

                    newJProperty("age", persons.Select( p => p.Age)),

                    newJProperty("funny", true),

                    newJProperty("array", new JArray(new[] { 2, 4, 1 }))

                    );

   log(jo);

   // JObject 操作

   var css = "{ /"#header/" :{background:/"red/"}, layout : [5,4,1] }";

   var style = JObject.Parse(css);

   var bd = new JObject();

   bd["color"] = "1px solid cyan";

   style["border"] = bd;

   var hd = style["#header"];

   style["body>div+p"] = hd;

   hd.Parent.Remove();

   style["layout"][0] = 22;

   log(style);

輸出:

    {

     "age": [

       12,

       28

     ],

     "funny": true,

     "array": [

       2,

       4,

       1

     ]

    }

    {

     "layout": [

       22,

       4,

       1

     ],

     "border": {

       "color": "1px solid cyan"

     },

     "body>div+p": {

       "background": "red"

     }

    }

來自:http://www.mzwu.com/article.asp?id=1913

1.6         參考網上的例子自己實現的一個封裝JsonNet的類,可控制時間的格式化

    ///<summary>

    ///主要使用了Newtonsoft.Json使用了物件與Json字串之間的互轉

    ///</summary>

    public class JsonToolEx

    {

        public static string ToJson(object obj,stringdateTimeFormat = "yyyy-MM-dd")

        {

            IsoDateTimeConvertertimeConverter =new IsoDateTimeConverter();

            timeConverter.DateTimeFormat =dateTimeFormat;

            returnJsonConvert.SerializeObject(obj,timeConverter);

        }

        public static T ToObject<T>(stringjson)

        {

            returnJsonConvert.DeserializeObject<T>(json);

        }

    }

1.7         使用LitJson

    public class JsonToolLit

    {

        public static string ToJson(object obj)

        {

            returnJsonMapper.ToJson(obj);

        }

        public static T ToObject<T>(stringjson)

        {

            returnJsonMapper.ToObject<T>(json);

        }

    }

2    解決json日期格式問題的3種方法

開發中有時候需要從伺服器端返回json格式的資料,在後臺程式碼中如果有DateTime型別的資料使用系統自帶的工具類序列化後將得到一個很長的數字表示日期資料,如下所示:

複製程式碼程式碼如下:

//設定伺服器響應的結果為純文字格式

           context.Response.ContentType = "text/plain";

          //學生物件集合

           List<Student> students = new List<Student>

           {

                new Student(){Name ="Tom",

                    Birthday=Convert.ToDateTime("2014-01-31 12:12:12")},

                new Student(){Name="Rose",

                    Birthday=Convert.ToDateTime("2014-01-10 11:12:12")},

                new Student(){Name="Mark",

                    Birthday=Convert.ToDateTime("2014-01-09 10:12:12")}

           };

           //javascript序列化器

           JavaScriptSerializer jss=new JavaScriptSerializer();

          //序列化學生集合物件得到json字元

           string studentsJson=jss.Serialize(students);

          //將字串響應到客戶端

           context.Response.Write(studentsJson);

          context.Response.End();

執行結果是:

其中Tom所對應生日“2014-01-31”變成了1391141532000,這其實是1970 年 1 月 1 日至今的毫秒數;1391141532000/1000/60/60/24/365=44.11年,44+1970=2014年,按這種方法可以得出年月日時分秒和毫秒。這種格式是一種可行的表示形式但不是普通人可以看懂的友好格式,怎麼讓這個格式變化?

解決辦法:

2.1         在伺服器端將日期格式使用Select方法或LINQ表示式轉換後發到客戶端:

using System;

using System.Collections.Generic;

using System.Web;

using System.Web.Script.Serialization;

namespace JsonDate1

{

   using System.Linq;

   /// <summary>

   /// 學生類,測試用

   /// </summary>

   public class Student

    {

       /// <summary>

       /// 姓名

       /// </summary>

       public String Name { get; set; }

       /// <summary>

       /// 生日

       /// </summary>

       public DateTime Birthday { get; set; }

    }

   /// <summary>

   /// 返回學生集合的json字元

   /// </summary>

   public class GetJson : IHttpHandler

    {

       public void ProcessRequest(HttpContext context)

       {

           //設定伺服器響應的結果為純文字格式

           context.Response.ContentType = "text/plain";

           //學生物件集合

           List<Student> students = new List<Student>

           {

                new Student(){Name="Tom",Birthday =Convert.ToDateTime("2014-01-31 12:12:12")},

                new Student(){Name="Rose",Birthday =Convert.ToDateTime("2014-01-1011:12:12")},

                new Student(){Name="Mark",Birthday =Convert.ToDateTime("2014-01-0910:12:12")}

           };

           //使用Select方法重新投影物件集合將Birthday屬性轉換成一個新的屬性

           //注意屬性變化後要重新命名,並立即執行

           var studentSet =

                students.Select

                (

                p => new { p.Name, Birthday= p.Birthday.ToString("yyyy-mm-dd") }

                ).ToList();

           //javascript序列化器

           JavaScriptSerializer jss = new JavaScriptSerializer();

           //序列化學生集合物件得到json字元

           string studentsJson = jss.Serialize(studentSet);

           //將字串響應到客戶端

           context.Response.Write(studentsJson);

           context.Response.End();

       }

       public bool IsReusable

       {

           get

           {

                return false;

           }

       }

    }

}

Select方法重新投影物件集合將Birthday屬性轉換成一個新的屬性,注意屬性變化後要重新命名,屬性名可以相同;這裡可以使用select方法也可以使用LINQ查詢表示式,也可以選擇別的方式達到相同的目的;這種辦法可以將集合中客戶端不用的屬性剔除,達到簡單優化效能的目的。

執行結果:

這時候的日期格式就已經變成友好格式了,不過在javascript中這只是一個字串。

2.2         方法二:在javascript處理

在javascript中將"Birthday":"\/Date(1391141532000)\/"中的字串轉換成javascript中的日期物件,可以將Birthday這個Key所對應的Value中的非數字字元以替換的方式刪除,到到一個數字1391141532000,然後例項化一個Date物件,將1391141532000毫秒作為引數,得到一個javascript中的日期物件,程式碼如下:

<!DOCTYPE html>

<htmlxmlns="http://www.w3.org/1999/xhtml">

<head>

   <title>json日期格式處理</title>

   <script src="Scripts/jquery-1.10.2.min.js"type="text/javascript"></script>

   <script type="text/javascript">

       $(function() {

           $.getJSON("getJson.ashx", function (students) {

                $.each(students, function(index, obj) {

                   $("<li/>").html(obj.Name).appendTo("#ulStudents");

                    //使用正則表示式將生日屬性中的非數字(\D)刪除

                    //並把得到的毫秒數轉換成數字型別

                    var birthdayMilliseconds =parseInt(obj.Birthday.replace(/\D/igm, ""));

                    //例項化一個新的日期格式,使用1970 年 1 月 1 日至今的毫秒數為引數

                    var birthday = newDate(birthdayMilliseconds);

                   $("<li/>").html(birthday.toLocaleString()).appendTo("#ulStudents");;

                });

           });

       });

   </script>

</head>

<body>

   <h2>json日期格式處理</h2>

   <ul id="ulStudents">

   </ul>

</body>

</html>

執行結果:

上的使用正則/\D/igm達到替換所有非數字的目的,\D表示非數字,igm是引數,分別表示忽視(ignore)大小寫;多次、全域性(global)替換;多行替換(multi-line);有一些時候還會出現+86的情況,只需要變換正則同樣可以達到目的。另外如果專案中反覆出現這種需要處理日期格式的問題,可以擴充套件一個javascript方法,程式碼如下:

$(function () {

           $.getJSON("getJson.ashx", function (students) {

                $.each(students, function(index, obj) {

                 $("<li/>").html(obj.Name).appendTo("#ulStudents");

                  //使用正則表示式將生日屬性中的非數字(\D)刪除

                    //並把得到的毫秒數轉換成數字型別

                    var birthdayMilliseconds =parseInt(obj.Birthday.replace(/\D/igm, ""));

                  //例項化一個新的日期格式,使用1970 年 1 月 1 日至今的毫秒數為引數

                    var birthday = newDate(birthdayMilliseconds);

                  $("<li/>").html(birthday.toLocaleString()).appendTo("#ulStudents");

                 $("<li/>").html(obj.Birthday.toDate()).appendTo("#ulStudents");

                });

           });

       });

       //在String物件中擴充套件一個toDate方法,可以根據要求完善

       String.prototype.toDate = function () {

           var dateMilliseconds;

           if (isNaN(this)) {

                //使用正則表示式將日期屬性中的非數字(\D)刪除

                dateMilliseconds=this.replace(/\D/igm, "");

           } else {

                dateMilliseconds=this;

            }

           //例項化一個新的日期格式,使用1970 年 1 月 1 日至今的毫秒數為引數

           return new Date(parseInt(dateMilliseconds));

       };

上面擴充套件的方法toDate不一定合理,也不夠強大,可以根據需要修改。

2.3         方法三:選擇一些第三方的json工具類

可以選擇一些第三方的json工具類,其中不乏有一些已經對日期格式問題已處理好了的,常見的json序列化與反序列化工具庫有:

1.fastJSON.

2.JSON_checker.

3.Jayrock.

4.Json.NET - LINQ to JSON.

5.LitJSON.

6.JSON for .NET.

7.JsonFx.

8.JSONSharp.

9.JsonExSerializer.

10.fluent-json

11.Manatee Json

這裡以litjson為序列化與反序列化json的工具類作示例,程式碼如下:

複製程式碼程式碼如下:

using System;

using System.Collections.Generic;

using System.Web;

using LitJson;

namespace JsonDate2

{

   using System.Linq;

   /// <summary>

   /// 學生類,測試用

   /// </summary>

   public class Student

    {

       /// <summary>

       /// 姓名

       /// </summary>

       public String Name { get; set; }

       /// <summary>

       /// 生日

       /// </summary>

       public DateTime Birthday { get; set; }

    }

   /// <summary>

   /// 返回學生集合的json字元

   /// </summary>

   public class GetJson : IHttpHandler

    {

       public void ProcessRequest(HttpContext context)

       {

           //設定伺服器響應的結果為純文字格式

           context.Response.ContentType = "text/plain";

           //學生物件集合

           List<Student> students = new List<Student>

           {

                new Student(){Name="Tom",Birthday =Convert.ToDateTime("2014-01-3112:12:12")},

                new Student(){Name="Rose",Birthday =Convert.ToDateTime("2014-01-1011:12:12")},

                new Student(){Name="Mark",Birthday =Convert.ToDateTime("2014-01-0910:12:12")}

           };

           //序列化學生集合物件得到json字元

           string studentsJson = JsonMapper.ToJson(students);

           //將字串響應到客戶端

           context.Response.Write(studentsJson);

           context.Response.End();

       }

       public bool IsReusable

       {

           get

           {

                return false;

           }

       }

    }

}

執行結果如下:

這時候的日期格式就基本正確了,只要在javascript中直接例項化日期就好了,

var date = new Date("01/31/201412:12:12");

alert(date.toLocaleString());

客戶端的程式碼如下:

複製程式碼程式碼如下:

$(function () {

           $.getJSON("GetJson2.ashx", function (students) {

                $.each(students, function(index, obj) {

                   $("<li/>").html(obj.Name).appendTo("#ulStudents");

                    var birthday = newDate(obj.Birthday);

                   $("<li/>").html(birthday.toLocaleString()).appendTo("#ulStudents");

                });

           });

       });

       var date = new Date("01/31/2014 12:12:12");

       alert(date.toLocaleString());