0

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

 1 year ago
source link: https://blog.darkthread.net/blog/jsonnet-dyna-prop-serialization/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【答客問】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的景仰如淊淊江水,綿綿不絕~

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

and has 8 comments

Comments

# 2016-08-31 11:53 PM by JRyo

黑暗大你好

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

能否在序列化時 動態設定是否需要讀取 json Attbuite? (在功能面上 會用到同一個class做兩個序列化 一個是完整序列化 一個是為了縮短json字串 所以刪減物件的內容 而因為物件包含的屬性太多樣 與多層 所以直接寫在物件上面 )

有到json.net尋找另外寫 contractResolver的方式 http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm

但是 ShouldSerializeContractResolver 卻找不到class與引用

能否幫忙解惑 感謝

# 2016-09-02 01:02 AM by JRyo

Hi 黑暗大 感謝回復 抱歉 我說明一下 在Setting 寫入 ContractResolver的部分沒有問題 另外 實作 ShouldSerializeContractResolver 我發現我寫錯的地方了

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

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

public class a { [jsonignore] int intA {get;set;}

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

或是 要用 JsonProperty 的哪個屬性

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

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

Post a comment

Comment
Name Captcha 75 + 25 =

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK