Conditional member serialization based on query parameter?

浪子不回头ぞ 提交于 2019-12-28 04:33:12

问题


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 this to reduce bandwidth on GETs without causing a proliferation of ViewModel classes. For example:

GET /books/1?format=summary

public class Book
{
    [SerializeFormat("summary")]
    public int Id { get; set; }

    [SerializeFormat("summary")]
    public string Title { get; set; }

    public string Contents { get; set; }
}

or

[SerializeFormat("summary","Id","Title")]
public class Book
{ ... }

To do this myself, I could derive all of my model classes from a custom base implementing ISerializable. In ISerializable.GetObjectData(), iterate through all properties inspecting the attributes. Not sure about performance on this idea.

Don't want to reinvent this solution, though if it already exists as a package.


回答1:


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.



来源:https://stackoverflow.com/questions/29713847/conditional-member-serialization-based-on-query-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!