【答客問】Json.NET-動態決定屬性是否序列化

昨天提到Json.NET屬性序列化設定,接獲讀者森哥留言:

請問黑大,
針對不需要序列化的「屬性」是否可以透過程式「動態」設定或是過濾?

有預感遲早也會遇到這個靠杯火盃的考驗,決定打鐵趁熱,馬上來練習。所幸,Json.NET真的很強大,早就料想到此一需求,提供ContractResolver以實現神乎奇技的高度動態化。

我寫了一個範例,展示兩種動態決定應序列化屬性的情境:

  • Serialize時傳入屬性名稱陣列作為參數,正向表列JSON應包含的屬性。
  • 由物件屬性值決定屬性是否要序列化,例如: 如果是女生就不包含年齡。(這幾乎已彈性到極點,雖然實務上不常用到)

程式的做法是宣告兩個繼承自DefaultContractResolver的類別: LimitPropsContractResolver在建構時傳入string[]參數列出要序列化的屬性名稱,並覆寫CreateProperties方法,過濾base.CreateProperties()傳回的IList<JsonProperty>,只保留前述string[]有列出的屬性;HideAgeContractResolver則覆寫CreateProperty()方法,由base.CreateProperty()取得JsonProperty,JsonProperty有個ShouldSerialize屬性可以傳入Lambda運算式,逐筆處理每個要序列化的物件,在Lambda運算式中可將物件轉型為原型別進行判斷,若不要序列化就傳回false。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
 
namespace ConsoleApplication1
{
    class Program
    {
        public enum Gender 
        {
            Male, Female
        }
 
        public class Person
        {
            public string Name { get; set; }
            [JsonConverter(typeof(StringEnumConverter))]
            public Gender Gender { get; set; }
            public int Age { get; set; }
            public Person(string name, Gender gender, int age)
            {
                Name = name; Gender = gender; Age = age;
            }
        }
 
        public class HideAgeContractResolver : DefaultContractResolver
        {
            //REF: http://james.newtonking.com/projects/json/help/index.html?topic=html/ContractResolver.htm
            protected override JsonProperty CreateProperty(MemberInfo member, 
                MemberSerialization memberSerialization)
            {
                JsonProperty p = base.CreateProperty(member, memberSerialization);
                if (p.PropertyName == "Age")
                {
                    //依性別決定是否要序列化
                    p.ShouldSerialize = instance =>
                    {
                        Person person = (Person)instance;
                        return person.Gender == Gender.Male;
                    };
                }
                return p;
            }
        }
 
        public class LimitPropsContractResolver : DefaultContractResolver
        {
            string[] props = null;
            public LimitPropsContractResolver(string[] props)
            {
                //指定要序列化屬性的清單
                this.props = props;
            }
            //REF: http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx
            protected override IList<JsonProperty> CreateProperties(Type type, 
                MemberSerialization memberSerialization)
            {
                IList<JsonProperty> list = 
                    base.CreateProperties(type, memberSerialization);
                //只保留清單有列出的屬性
                return list.Where(p => props.Contains(p.PropertyName)).ToList();
            }
 
        }
 
        static void Main(string[] args)
        {
            List<Person> list = new List<Person>();
            list.Add(new Person("George", Gender.Male, 18));
            list.Add(new Person("Mary", Gender.Female, 40));
            //正常輸出
            Console.WriteLine(JsonConvert.SerializeObject(
                list, Formatting.Indented));
            var settings = new JsonSerializerSettings();
            //加上ContractResolver,正向表列哪些屬性要序列化
            settings.ContractResolver = 
                new LimitPropsContractResolver("Name,Age".Split(','));
            Console.WriteLine(JsonConvert.SerializeObject(
                list, Formatting.Indented, settings));
            //加上ContractResolver,依物件的屬性值動態決定要不要序列化
            settings.ContractResolver = new HideAgeContractResolver();
            Console.WriteLine(JsonConvert.SerializeObject(
                list, Formatting.Indented, settings));
            Console.ReadLine();
 
        }
    }
}

程式執行結果如下,共有三段輸出,第一段為正常版;第二段套用LimitPropsContractResolver("Name,Age".Split(',')),故JSON中只見Name及Age,Gender被隱藏;第三段套用了HideAgeContractResolver(),如結果所示,Mary的JSON內容不包含年齡,George則包含。

[
  {
    "Name": "George",
    "Gender": "Male",
    "Age": 18
  },
  {
    "Name": "Mary",
    "Gender": "Female",
    "Age": 40
  }
]
[
  {
    "Name": "George",
    "Age": 18
  },
  {
    "Name": "Mary",
    "Age": 40
  }
]
[
  {
    "Name": "George",
    "Gender": "Male",
    "Age": 18
  },
  {
    "Name": "Mary",
    "Gender": "Female"
  }
]

演練完畢,內心激動澎湃,對Json.NET的景仰如淊淊江水,綿綿不絕~

如果奧斯卡有最佳元件獎,我提名它!

歡迎推文分享:
Published 04 September 2013 09:26 PM 由 Jeffrey
Filed under: ,
Views: 7,347



意見

# 森哥 said on 04 September, 2013 09:48 AM

感謝黑大的解答,小弟對您的景仰如滔滔江水...

# JRyo said on 31 August, 2016 11:53 PM

黑暗大你好

想問一下 已經在class上 加上了 JsonIgnore

能否在序列化時 動態設定是否需要讀取 json Attbuite?

(在功能面上 會用到同一個class做兩個序列化  

一個是完整序列化  一個是為了縮短json字串 所以刪減物件的內容

而因為物件包含的屬性太多樣 與多層 所以直接寫在物件上面 )

有到json.net尋找另外寫 contractResolver的方式

www.newtonsoft.com/.../ConditionalProperties.htm

但是 ShouldSerializeContractResolver  卻找不到class與引用

能否幫忙解惑 感謝

# Jeffrey said on 01 September, 2016 09:06 AM

to JRyo, 問一下,序列化時有JsonConvert.SerializeObject(book, Formatting.Indented,

new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver() }); 嗎? 參考:

http://goo.gl/49Vo17

# JRyo said on 02 September, 2016 01:02 AM

Hi 黑暗大 感謝回復

抱歉 我說明一下

在Setting 寫入 ContractResolver的部分沒有問題

另外 實作 ShouldSerializeContractResolver   我發現我寫錯的地方了

但是 不確定在 ContractReolver 內該用甚麼方法 取消掉 JsonProperty 的 jsonignore

這個問題才是我真的想要問的

public class a

{

[jsonignore]

int intA {get;set;}

}

如上  是否在 json Setting 內 已有方法或類別可以控制

或是 要用 JsonProperty 的哪個屬性

讓我套用Contract時可以選擇忽略 ignore 的attribute

另外在請問一下 在一個Setting內 只能寫入一個 ConTractResolver嗎  如果我有兩個規則 是否只能選擇一個用  

# Jeffrey said on 02 September, 2016 07:49 PM

to JRyo, 偵測Property有沒有加[JsonIgnored]可以使用PropertyInfo.GetCustomAttribute() 參考:stackoverflow.com/.../288936 。至於ContractResolver應該只能指定一個,若想綜合多個ContractResolver的邏輯,可以寫一個整合版,在其中依不同狀況執行不同邏輯。

# JRyo said on 05 September, 2016 01:31 AM

Hi 黑暗大 感謝回復

所以看起來 還是要讓他在序列化時一個一個處理的樣子

我會去研究看看 感謝給予資源 謝謝

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<September 2013>
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication