Conditional member serialization based on query parameter?

前端 未结 1 826
闹比i
闹比i 2020-12-04 03:27

I would like to control which properties from my model are serialized to my WebAPI2 JSON response, based on matching a query parameter to an attribute. I mainly want to do t

相关标签:
1条回答
  • 2020-12-04 03:58

    One possibility would be to introduce a custom attribute JsonConditionalIncludeAttribute that can be applied to properties and fields:

    [System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
    public class JsonConditionalIncludeAttribute : System.Attribute
    {
        public JsonConditionalIncludeAttribute(string filterName)
        {
            this.FilterName = filterName;
        }
    
        public string FilterName { get; private set; }
    }
    

    Next, subclass DefaultContractResolver, override CreateProperty, and return null for properties that have at least one [JsonConditionalInclude] applied, none of which match the a filter supplied to the contract resolver:

    public class JsonConditionalIncludeContractResolver : DefaultContractResolver
    {
        public JsonConditionalIncludeContractResolver(string filterName)
        {
            this.FilterName = filterName;
        }
    
        public string FilterName { get; set; }
    
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);
            // Properties without JsonConditionalIncludeAttribute applied are serialized unconditionally.
            // Properties with JsonConditionalIncludeAttribute are serialized only if one of the attributes
            // has a matching filter name.
            var attrs = property.AttributeProvider.GetAttributes(typeof(JsonConditionalIncludeAttribute), true);
            if (attrs.Count > 0 && !attrs.Cast<JsonConditionalIncludeAttribute>().Any(a => a.FilterName == FilterName))
                return null;
            return property;
        }
    }
    

    Finally, when serializing your class to JSON, set JsonSerializerSettings.ContractResolver equal to your custom contract resolver, initializing the FilterName from your web request, for instance:

    public class TestClass
    {
        public string Property1 { get; set; }
    
        [JsonConditionalInclude("summary")]
        [JsonConditionalInclude("title")]
        public string Property2 { get; set; }
    
        [JsonConditionalInclude("summary")]
        public string Property3 { get; set; }
    
        [JsonConditionalInclude("title")]
        [JsonConditionalInclude("citation")]
        public string Property4 { get; set; }
    
        [JsonConditionalInclude("citation")]
        public string Field1;
    
        public static void Test()
        {
            var test = new TestClass { Property1 = "a", Property2 = "b", Property3 = "c", Property4 = "d", Field1 = "e" };
            Test(test, "summary"); // Prints "a", "b" and "c"
            Test(test, "title");   // Prints "a", "b" and "d".
            Test(test, "citation");// Prints "e", "a" and "d"
            Test(test, null);      // Prints "e", "a", "b", "c" and "d".
        }
    
        public static string Test(TestClass test, string webRequestFormat)
        {
            var settings = new JsonSerializerSettings { ContractResolver = new JsonConditionalIncludeContractResolver(webRequestFormat) };
    
            var json = JsonConvert.SerializeObject(test, Formatting.Indented, settings);
    
            Debug.WriteLine(json);
            return json;
        }
    }
    

    The contract resolver will apply to all classes being serialized, not just the root class, which looks to be what you want.

    0 讨论(0)
提交回复
热议问题