【轉】C#解析Json Newtonsoft.Json


Newtonsoft.Json源碼

Newtonsoft.Json介紹

  在做開發的時候,很多數據交換都是以json格式傳輸的。而使用Json的時候,我們很多時候會涉及到幾個序列化對象的使用:DataContractJsonSerializer,JavaScriptSerializer  Json.NET即Newtonsoft.Json。大多數人都會選擇性能以及通用性較好Json.NET,這個不是微軟的類庫,但是一個開源的世界級的Json操作類庫,從下面的性能對比就可以看到它的其中之一的性能優點。

齊全的API介紹,使用方式簡單

 

基本用法

  Json.Net是支持序列化和反序列化DataTable,DataSet,Entity Framework和Entity的。下面分別舉例說明序列化和反序列化。

DataTable:

           //序列化DataTable
            DataTable dt = new DataTable();
            dt.Columns.Add("Age", Type.GetType("System.Int32"));
            dt.Columns.Add("Name", Type.GetType("System.String"));
            dt.Columns.Add("Sex", Type.GetType("System.String"));
            dt.Columns.Add("IsMarry", Type.GetType("System.Boolean"));
            for (int i = 0; i < 4; i++)
            {
                DataRow dr = dt.NewRow();
                dr["Age"] = i + 1;
                dr["Name"] = "Name" + i;
                dr["Sex"] = i % 2 == 0 ? "" : "";
                dr["IsMarry"] = i % 2 > 0 ? true : false;
                dt.Rows.Add(dr);
            }
            Console.WriteLine(JsonConvert.SerializeObject(dt));

利用上面字符串進行反序列化

 string json = JsonConvert.SerializeObject(dt);
 dt=JsonConvert.DeserializeObject<DataTable>(json);
 foreach (DataRow dr in dt.Rows)
 {
   Console.WriteLine("{0}\t{1}\t{2}\t{3}\t", dr[0], dr[1], dr[2], dr[3]);
 }

Entity序列化和DataTable一樣,就不過多介紹了。

高級用法

    1.忽略某些屬性

    2.默認值的處理

    3.空值的處理

    4.支持非公共成員

    5.日期處理

    6.自定義序列化的字段名稱

  7.動態決定屬性是否序列化

    8.枚舉值的自定義格式化問題

  9.自定義類型轉換

  10.全局序列化設置

 一.忽略某些屬性

  類似本問開頭介紹的接口優化,實體中有些屬性不需要序列化返回,可以使用該特性。首先介紹Json.Net序列化的模式:OptOut 和 OptIn

OptOut 默認值,類中所有公有成員會被序列化,如果不想被序列化,可以用特性JsonIgnore
OptIn 默認情況下,所有的成員不會被序列化,類中的成員只有標有特性JsonProperty的才會被序列化,當類的成員很多,但客戶端僅僅需要一部分數據時,很有用

 

 

 僅需要姓名屬性

 [JsonObject(MemberSerialization.OptIn)]
    public class Person
    {
        public int Age { get; set; }

        [JsonProperty]
        public string Name { get; set; }

        public string Sex { get; set; }

        public bool IsMarry { get; set; }

        public DateTime Birthday { get; set; }
    }

  不需要是否結婚屬性

[JsonObject(MemberSerialization.OptOut)]
    public class Person
    {
        public int Age { get; set; }

        public string Name { get; set; }

        public string Sex { get; set; }

        [JsonIgnore]
        public bool IsMarry { get; set; }

        public DateTime Birthday { get; set; }
    }

  通過上面的例子可以看到,要實現不返回某些屬性的需求很簡單。1.在實體類上加上[JsonObject(MemberSerialization.OptOut)] 2.在不需要返回的屬性上加上 [JsonIgnore]說明。

二.默認值處理

    序列化時想忽略默認值屬性可以通過JsonSerializerSettings.DefaultValueHandling來確定,該值為枚舉值

DefaultValueHandling.Ignore
序列化和反序列化時,忽略默認值
DefaultValueHandling.Include
序列化和反序列化時,包含默認值

 

 

 

[DefaultValue(10)]
 public int Age { get; set; }
 Person p = new Person { Age = 10, Name = "張三豐", Sex = "", IsMarry = false, Birthday = new DateTime(1991, 1, 2) };
 JsonSerializerSettings jsetting=new JsonSerializerSettings();
 jsetting.DefaultValueHandling=DefaultValueHandling.Ignore;
 Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

最終結果如下:

 

三.空值的處理

  序列化時需要忽略值為NULL的屬性,可以通過JsonSerializerSettings.NullValueHandling來確定,另外通過JsonSerializerSettings設置屬性是對序列化過程中所有屬性生效的,想單獨對某一個屬性生效可以使用JsonProperty,下面將分別展示兩個方式

  1.JsonSerializerSettings

Person p = new Person { room=null,Age = 10, Name = "張三豐", Sex = "", IsMarry = false, Birthday = new DateTime(1991, 1, 2) };
 JsonSerializerSettings jsetting=new JsonSerializerSettings();
 jsetting.NullValueHandling = NullValueHandling.Ignore;
 Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

  

   2.JsonProperty

通過JsonProperty屬性設置的方法,可以實現某一屬性特別處理的需求,如默認值處理,空值處理,自定義屬性名處理,格式化處理。上面空值處理實現

[JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
 public Room room { get; set; }

四.支持非公共成員

  序列化時默認都是處理公共成員,如果需要處理非公共成員,就要在該成員上加特性"JsonProperty"

[JsonProperty]
 private int Height { get; set; }

五.日期處理

  對於Dateime類型日期的格式化就比較麻煩了,系統自帶的會格式化成iso日期標准,但是實際使用過程中大多數使用的可能是yyyy-MM-dd 或者yyyy-MM-dd HH:mm:ss兩種格式的日期,解決辦法是可以將DateTime類型改成string類型自己格式化好,然后在序列化。如果不想修改代碼,可以采用下面方案實現。

      Json.Net提供了IsoDateTimeConverter日期轉換這個類,可以通過JsnConverter實現相應的日期轉換

[JsonConverter(typeof(IsoDateTimeConverter))]
    public DateTime Birthday { get; set; }

但是IsoDateTimeConverter日期格式不是我們想要的,我們可以繼承該類實現自己的日期

public class ChinaDateTimeConverter : DateTimeConverterBase
    {
        private static IsoDateTimeConverter dtConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd" };

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return dtConverter.ReadJson(reader, objectType, existingValue, serializer);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            dtConverter.WriteJson(writer, value, serializer);
        }
    }

   自己實現了一個yyyy-MM-dd格式化轉換類,可以看到只是初始化IsoDateTimeConverter時給的日期格式為yyyy-MM-dd即可,下面看下效果

[JsonConverter(typeof(ChinaDateTimeConverter))]
public DateTime Birthday { get; set; }

   可以根據自己需求實現不同的轉換類

 

六.自定義序列化的字段名稱

    實體中定義的屬性名可能不是自己想要的名稱,但是又不能更改實體定義,這個時候可以自定義序列化字段名稱。

[JsonProperty(PropertyName = "CName")]
     public string Name { get; set; }

七.動態決定屬性是否序列化

  這個是為了實現@米粒兒提的需求特別增加的,根據某些場景,可能A場景輸出A,B,C三個屬性,B場景輸出E,F屬性。雖然實際中不一定存在這種需求,但是json.net依然可以支持該特性。

  繼承默認的DefaultContractResolver類,傳入需要輸出的屬性

     重寫修改了一下,大多數情況下應該是要排除的字段少於要保留的字段,  為了方便書寫這里修改了構造函數加入retain表示props是需要保留的字段還是要排除的字段

public class LimitPropsContractResolver : DefaultContractResolver
    {
        string[] props = null;

        bool retain;

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="props">傳入的屬性數組</param>
        /// <param name="retain">true:表示props是需要保留的字段  false:表示props是要排除的字段</param>
        public LimitPropsContractResolver(string[] props, bool retain=true)
        {
            //指定要序列化屬性的清單
            this.props = props;

            this.retain = retain;
        }

        protected override IList<JsonProperty> CreateProperties(Type type,

        MemberSerialization memberSerialization)
        {
            IList<JsonProperty> list =
            base.CreateProperties(type, memberSerialization);
            //只保留清單有列出的屬性
            return list.Where(p => {
                if (retain)
                {
                    return props.Contains(p.PropertyName);
                }
                else
                {
                    return !props.Contains(p.PropertyName);
                }      
            }).ToList();
        }
 public int Age { get; set; }

        [JsonIgnore]
        public bool IsMarry { get; set; }

        public string Sex { get; set; }
JsonSerializerSettings jsetting=new JsonSerializerSettings();
      jsetting.ContractResolver = new LimitPropsContractResolver(new string[] { "Age", "IsMarry" });
      Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

使用自定義的解析類,只輸出"Age", "IsMarry"兩個屬性,看下最終結果.只輸出了Age屬性,為什么IsMarry屬性沒有輸出呢,因為標注了JsonIgnore

 

 看到上面的結果想要實現pc端序列化一部分,手機端序列化另一部分就很簡單了吧,我們改下代碼實現一下

string[] propNames = null;
  if (p.Age > 10)
  {
    propNames = new string[] { "Age", "IsMarry" };
  }
  else
  {
      propNames = new string[] { "Age", "Sex" };
  }
  jsetting.ContractResolver = new LimitPropsContractResolver(propNames);
  Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

八.枚舉值的自定義格式化問題

  默認情況下對於實體里面的枚舉類型系統是格式化成改枚舉對應的整型數值,那如果需要格式化成枚舉對應的字符怎么處理呢?Newtonsoft.Json也幫我們想到了這點,下面看實例

public enum NotifyType
    {
        /// <summary>
        /// Emil發送
        /// </summary>
        Mail=0,

        /// <summary>
        /// 短信發送
        /// </summary>
        SMS=1
    }

    public class TestEnmu
    {
        /// <summary>
        /// 消息發送類型
        /// </summary>
        public NotifyType Type { get; set; }
    }
    JsonConvert.SerializeObject(new TestEnmu());

輸出結果:  現在改造一下,輸出"Type":"Mail"

public class TestEnmu
    {
        /// <summary>
        /// 消息發送類型
        /// </summary>
        [JsonConverter(typeof(StringEnumConverter))]
        public NotifyType Type { get; set; }
    }

其它的都不變,在Type屬性上加上了JsonConverter(typeof(StringEnumConverter))表示將枚舉值轉換成對應的字符串,而StringEnumConverter是Newtonsoft.Json內置的轉換類型,最終輸出結果

 

九.自定義類型轉換

默認情況下對於實體里面的Boolean系統是格式化成true或者false,對於true轉成"是" false轉成"否"這種需求改怎么實現了?我們可以自定義類型轉換實現該需求,下面看實例

public class BoolConvert : JsonConverter
    {
        private string[] arrBString { get; set; }

        public BoolConvert()
        {
            arrBString = "是,否".Split(',');
        }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="BooleanString">將bool值轉換成的字符串值</param>
        public BoolConvert(string BooleanString)
        {
            if (string.IsNullOrEmpty(BooleanString))
            {
                throw new ArgumentNullException();
            }
            arrBString = BooleanString.Split(',');
            if (arrBString.Length != 2)
            {
                throw new ArgumentException("BooleanString格式不符合規定");
            }
        }


        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            bool isNullable = IsNullableType(objectType);
            Type t = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;

            if (reader.TokenType == JsonToken.Null)
            {
                if (!IsNullableType(objectType))
                {
                    throw new Exception(string.Format("不能轉換null value to {0}.", objectType));
                }

                return null;
            }

            try
            {
                if (reader.TokenType == JsonToken.String)
                {
                    string boolText = reader.Value.ToString();
                    if (boolText.Equals(arrBString[0], StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                    else if (boolText.Equals(arrBString[1], StringComparison.OrdinalIgnoreCase))
                    {
                        return false;
                    }
                }

                if (reader.TokenType == JsonToken.Integer)
                {
                    //數值
                    return Convert.ToInt32(reader.Value) == 1;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Error converting value {0} to type '{1}'", reader.Value, objectType));
            }
            throw new Exception(string.Format("Unexpected token {0} when parsing enum", reader.TokenType));
        }

        /// <summary>
        /// 判斷是否為Bool類型
        /// </summary>
        /// <param name="objectType">類型</param>
        /// <returns>為bool類型則可以進行轉換</returns>
        public override bool CanConvert(Type objectType)
        {
            return true;
        }


        public bool IsNullableType(Type t)
        {
            if (t == null)
            {
                throw new ArgumentNullException("t");
            }
            return (t.BaseType.FullName=="System.ValueType" && t.GetGenericTypeDefinition() == typeof(Nullable<>));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }

            bool bValue = (bool)value;

            if (bValue)
            {
                writer.WriteValue(arrBString[0]);
            }
            else
            {
                writer.WriteValue(arrBString[1]);
            }
        }
    }

自定義了BoolConvert類型,繼承自JsonConverter。構造函數參數BooleanString可以讓我們自定義將true false轉換成相應字符串。下面看實體里面怎么使用這個自定義轉換類型

public class Person
    {
        [JsonConverter(typeof(BoolConvert))]
        public bool IsMarry { get; set; }
    }

相應的有什么個性化的轉換需求,都可以使用自定義轉換類型的方式實現。

 

十.全局序列化設置

文章開頭提出了Null值字段怎么不返回的問題,相應的在高級用法也給出了相應的解決方案使用jsetting.NullValueHandling = NullValueHandling.Ignore; 來設置不返回空值。這樣有個麻煩的地方,每個不想返回空值的序列化都需設置一下。可以對序列化設置一些默認值方式么?下面將解答

 Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
   JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
   {
    //日期類型默認格式化處理
     setting.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
      setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";

    //空值處理
      setting.NullValueHandling = NullValueHandling.Ignore;

      //高級用法九中的Bool類型轉換 設置
      setting.Converters.Add(new BoolConvert("是,否"));

      return setting;
   });

 

動態改變屬性序列化名稱

"動態改變屬性序列化名稱"顧名思義:在不同場景下實體字段序列化后字段名稱不同,比如有下面實體A,正常序列化后json為{"Id":"123"}

public class A
    {
        public string Id { get; set; }
    }

 現在有兩種新場景A場景下 字段Id需要序列化為Key,B場景下字段Id需要序列化為id,那么如何在不改變實體代碼情形下完成該功能呢?下面以樹形結構數據為例子進行講解。

       各種各樣的前端樹形控件所要求數據格式不一樣,下面列舉幾種常見的樹形控件數據格式。

//bootstrap treeview,數據結構為
[
    {
            id:'1', //節點id
            text: '父節點',  //節點顯示文本
            icon: 'glyphicon glyphicon-cloud-download',  //節點圖標樣式
            nodes:[{id:'2',text:'子節點'}]  //子節點
    }
]

//zTree
[  
    { "id" : "1", "name" : "父節點1", "children" : [{id:'4',name:'子節點1'}] },  
    { "id" : "2", "name" : "父節點2", "children" : [{id:'5',name:'子節點2'}] },  
    { "id" : "3", "name" : "父節點3", "children" : [{id:'6',name:'子節點3'}] }  
]

 兩者之間字段對比

  treeview zTree
節點id id id
顯示文本 text name
圖標 icon icon
子節點 nodes children

 標紅部分是數據格式區別,假設后台定義的樹形實體如下

/// <summary>
    /// 樹形實體
    /// </summary>
    public class Tree
    {
        /// <summary>
        /// 當前ID
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        /// 文本
        /// </summary>
        public string Text { get; set; }

        /// <summary>
        /// 附加信息
        /// </summary>
        public string Tag { get; set; }

        /// <summary>
        /// 節點圖標
        /// </summary>
        public string Icon { get; set; }

        /// <summary>
        /// 子級
        /// </summary>
        public List<Tree> Childrens { get; set; }
    }
現在的情形是這樣的,后台樹形實體已經定義完成,前台樹形控件使用的是treeview。有什么辦法使后台序列化返回的json數據格式和控件所要求的保持一致呢。
方法一 修改實體Tree 
/// <summary>
    /// 樹形實體
    /// </summary>
    public class Tree
    {
        /// <summary>
        /// 當前ID
        /// </summary>
        public string id { get; set; }

        /// <summary>
        /// 文本
        /// </summary>
        public string text { get; set; }

        /// <summary>
        /// 附加信息
        /// </summary>
        public string Tag { get; set; }

        /// <summary>
        /// 節點圖標
        /// </summary>
        public string Icon { get; set; }

        /// <summary>
        /// 子級
        /// </summary>
        public List<Tree> nodes { get; set; }
    }

其中標紅部分是修改的,當然還需要修改對Tree實體賦值的代碼,這里就不列出了。

方法二 前台js處理

var data=[
            {"Id":"1","Text":"父節點1","Childrens":[
                {"Id":"3","Text":"子節點1","Childrens":[{"Id":"5","Text":"子節點1-1"}]},
                {"Id":"4","Text":"子節點2"}
            ]},
            {"Id":"2","Text":"父節點2","Childrens":[
                {"Id":"5","Text":"子節點3"}
            ]}]
        //將后台返回數據轉換成treeview所需格式數據
        handleChild(data);
        console.log(data);
        
        //轉換后台實體數據為treeview符合的數據格式
        function handleChild(childs){
            for(var i=0,length=childs.length;i<length;i++){
                var item=childs[i];
                item.id=item.Id;
                item.text=item.Text;
                item.nodes=item.Childrens;
                //處理子節點
                if(item.Childrens){
                    handleChild(item.Childrens);
                }
                delete item.Id;
                delete item.Text;
                delete item.Childrens;
            }
        }
 以上兩種方法都可以很輕松的解決我上述提出的問題,項目進行到一半,treeview使用的也很好,一切都很太平。某一天遇到了一個難題,前台有個功能需要使用zTree實現。但是需要保證之前使用treeView的功能模塊不變,又得支持zTree數據格式,先來分析一下上面兩種方案看還能不能繼續使用,方案一,可以新建一個樹形實體專門和zTree對應。方案二,重新實現一套數據轉換代碼。以上兩種方案缺點很明顯,前后端依賴太強,前台換了控件導致變動過大。
    在思考有沒有更好的解決方案時,我想到了高級序列化用法中 自定義序列化的字段名稱這一條,既然Newtonsoft.Json提供了實體字段A序列化成B的特性,那么現在唯一需要解決的問題:怎么動態修改這個映射關系。經過一番嘗試和閱讀源代碼,終於找到了下面最佳實踐。
/// <summary>
    /// 動態屬性轉換約定
    /// </summary>
    /// <remarks>
    /// 處理場景 樹形結構數據 后台代碼實體定義 為 Id Childrens 但是前台樹形控件所需數據結構為  id,nodes
    /// 這個時候可以使用該屬性約定轉換類 動態設置 序列化后字段名稱
    /// </remarks>
    /// <example>
    ///     JsonSerializerSettings setting = new JsonSerializerSettings();
    ///     setting.ContractResolver = new PropsContractResolver(new Dictionary<string, string> { { "Id", "id" }, { "Text", "text" }, { "Childrens", "nodes" } });
    ///     string AA = JsonConvert.SerializeObject(cc, Formatting.Indented, setting);
    /// </example>
    public class PropsContractResolver : DefaultContractResolver
    {
        Dictionary<string, string> dict_props = null;

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="props">傳入的屬性數組</param>
        public PropsContractResolver(Dictionary<string, string> dictPropertyName)
        {
            //指定字段要序列化成什么名稱
            this.dict_props = dictPropertyName;
        }

        protected override string ResolvePropertyName(string propertyName)
        {
            string newPropertyName = string.Empty;
            if (dict_props != null && dict_props.TryGetValue(propertyName, out newPropertyName))
            {
                return newPropertyName;
            }
            else
            {
                return base.ResolvePropertyName(propertyName);
            }
        }
    }

調用代碼示例

string type="zTree";
  //字段映射關系
  Dictionary<string, string> _dictProp = null;
  if(type=="zTree"){
    _dictProp = new Dictionary<string, string> { { "Icon", "icon" }, { "Text", "name" }, { "Childrens", "children" } };
  }else if(type=="treeview"){
    _dictProp = new Dictionary<string, string> { { "Icon", "icon" }, { "Text", "text" }, { "Childrens", "nodes" } };
  }
                 
  // 序列化設置
  JsonSerializerSettings PropSettings = new JsonSerializerSettings { 
    ContractResolver = new PropsContractResolver(_dictProp)
  };
  return JsonConvert.SerializeObject(new List<Tree>(), Formatting.None, PropSettings);
使用了 動態改變屬性序列化名稱方案后,前后台完全解綁了,不管前台使用什么樹形控件,后台實體只有一個樹形實體。我們要做的僅僅是設置一下字段映射關系而已。

枚舉值序列化問題

   默認情況下對於實體里面的枚舉類型系統是格式化成改枚舉對應的整型數值,那如果需要格式化成枚舉對應的字符怎么處理呢?Newtonsoft.Json也幫我們想到了這點,下面看實例

public enum NotifyType
    {
        /// <summary>
        /// Emil發送
        /// </summary>
        Mail=0,

        /// <summary>
        /// 短信發送
        /// </summary>
        SMS=1
    }

    public class TestEnmu
    {
        /// <summary>
        /// 消息發送類型
        /// </summary>
        public NotifyType Type { get; set; }
    }
    JsonConvert.SerializeObject(new TestEnmu());

輸出結果:  現在改造一下,輸出"Type":"Mail"

 public class TestEnmu
    {
        /// <summary>
        /// 消息發送類型
        /// </summary>
        [JsonConverter(typeof(StringEnumConverter))]
        public NotifyType Type { get; set; }
    }

其它的都不變,在Type屬性上加上了JsonConverter(typeof(StringEnumConverter))表示將枚舉值轉換成對應的字符串,而StringEnumConverter是Newtonsoft.Json內置的轉換類型,最終輸出結果

全局設置

  全局參數設置功能是我最喜歡使用的功能,現在做的mvc項目,我都會先設定空值處理,減少不必要的流量損耗。上篇文章開篇說了,最初研究Newtonsoft.Json是從移動端項目開始的,無用字段空值字段不返回。

Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
   JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
   {
    //日期類型默認格式化處理
     setting.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
      setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";

    //空值處理
      setting.NullValueHandling = NullValueHandling.Ignore;return setting;
   });

 

原文地址:https://www.cnblogs.com/yanweidie/p/4605212.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM