C# Newtonsoft.Json 高級用法


  最近在做接口開發,對方團隊開發了一個Web API 的接口,傳輸數據的格式是 JSON。當時看到這個東西,感覺很簡單,也沒想什么,沒用多久就完成了我的功能,我完成的功能很簡單,就是獲取數據,然后把數據列表進行 JSON 序列化,然后再以 POST 方式調用對方 Web Api 的接口,將 JSON 的數據一起傳遞過去,我想的很簡單,直接調用並返回結果就完成了。最后對方接口返回錯誤,提示從 傳遞過去的 JSON 數據中的第一個字段開始就取不到值。

  郁悶,為什么呢?我的參數也是按着他們接口的規范寫的,數據獲取也沒錯,JSON 格式化的數據好像也正常,對方團隊的 Api 接口為什么提示取不到值。后來,靜下心來,仔細看了看我的項目,應該沒啥大問題。突然,好像感覺找到了原因,但是感覺不應該是這個問題,那就是我傳遞的 JSON 數據的 Key 值的名稱采用的是 Pascal 命名法,就是每個單詞的首字符都是大寫的({“BoyFirend”:“xxx”,"HomeAddress":"xxxx"}),而對方的 Key 的命名法是 Camel 命名法(比如:{“boyFirend”:“xxx”,"homeAddress":"xxxx"})。之所以產生這個結果,因為 JSON 數據是直接通過 C#的實體類序列化而來的,在 C# 中的實體類的每個屬性的命名就是采用的是 Pascal 命名方法。找到了原因,但是不太確定,然后一試,還這是這樣原因。

  原因找到了,解決問題就簡單了,對方團隊寫的 Api 接口個人感覺兼容不好,不應該對大小寫敏感,讓接口更通用一點才好,我建議對方改一下接口,對方好像不怎么同意,那我只能修改我的接口了。說到 JSON 序列化,我使用的是 Newtonsoft.Json,這個東東,大家應該很熟悉吧。由於對它研究不深,就上網找了一下子,不錯,還真有解決辦法,那我也收藏一下,便於以后查閱。

 

一、Newtonsoft.Json介紹

  做 Web 開發的,沒有接觸過 JavaScript 的肯定很少,做前端開發,沒有接觸過Ajax的估計更不多了。現在的系統大多數是分布式系統,分布式系統就會涉及到數據的傳輸,JSON在數據傳輸和提交方面有着天生的優勢。當我們使用Json的時候,很多時候會涉及到幾個序列化對象的使用:DataContractJsonSerializer,JavaScriptSerializer  Json.NET即 Newtonsoft.Json。大多數人都會選擇性能以及通用性較好 Json.NET,這個不是微軟的類庫,是一個開源的Json操作類庫,速度比 DataContractJsonSerializer 快 50%,比 JavaScriptSerializer 快 250%,從下面的性能對比就可以看到它的其中之一的性能優點。

        

  它的確很快,使用也很簡單,它支持序列化的對象也很多,我剛開始以為只是支持 以實體類為基礎的序列化,其實還有很多,比如,數據表(DataTable),數據集(DataSet)等,你了解越深就越喜歡它,也避免你在不知的情況下“重復造輪子”。

 

二、基本用法

  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.全局序列化設置

   1.忽略某些屬性

  我們在序列化的過程中,並不是所有屬性都需要序列化的,如果實體中有些屬性不需要序列化,可以使用該特性。首先介紹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]說明。

  2.默認值處理

     序列化時想忽略默認值屬性可以通過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));

  最終結果如下:

   

  3.空值的處理

  序列化時需要忽略值為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; }

  4.支持非公共成員

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

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

  5.日期處理

  對於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; }

  

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

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

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

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

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

  這個是為了實現@米粒兒提的需求特別增加的,根據某些場景,可能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));

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

  默認情況下對於實體里面的枚舉類型系統是格式化成改枚舉對應的整型數值,那如果需要格式化成枚舉對應的字符怎么處理呢?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內置的轉換類型,最終輸出結果

 

  9.自定義類型轉換

  默認情況下對於實體里面的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; }
    }

   

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

  10.全局序列化設置

  文章開頭提出了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;
   });

   這樣設置以后,以后使用序列化的地方就不需要單獨設置了,個人最喜歡設置的是空值處理這一塊。

四、總結

    今天就到這里了,它涉及的內容太多了,我們只是接觸了皮毛,官網地址:https://www.newtonsoft.com/json


免責聲明!

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



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