How to exclude properties from JsonConvert.PopulateObject that don't exist in some base type or interface?

前端 未结 1 1829
予麋鹿
予麋鹿 2021-01-27 02:19

Is it possible to define options to JsonConvert.PopulateObject to exclude fields given in a json, that does not exists in the target object\'s interface implementation?



        
相关标签:
1条回答
  • 2021-01-27 02:52

    There is no simple setting in JsonSerializerSettings that will cause JsonConvert.PopulateObject() to populate an instance of a derived type as if it were an instance of some base type. To confirm this, you can check the source for JsonSerializerInternalReader.Populate(), which takes as arguments only a reader and target and pulls the target's contract directly from its type:

    public void Populate(JsonReader reader, object target)
    {
        Type objectType = target.GetType();
    
        JsonContract contract = Serializer._contractResolver.ResolveContract(objectType);
    

    Thus your options include:

    1. Modify the definition of your Element class and add [JsonIgnore] to the properties you don't want to populate.

      You probably don't want to do this because it will prevent the properties from ever being serialized or deserialized.

    2. Use a custom contract resolver to ignore all properties of Element that are not also properties of IElementWriter.

      This would seem to be the better solution.

    Assuming you choose option #2, you can introduce the following custom contract resolver:

    public class UpcastingContractResolver<TDerived, TBase> : DefaultContractResolver where TDerived: TBase
    {
        readonly HashSet<string> baseProperties = ((JsonObjectContract)JsonSerializer.Create().ContractResolver.ResolveContract(typeof(TBase)))
            .Properties.Select(p => p.UnderlyingName)
            .ToHashSet();
    
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var properties = base.CreateProperties(type, memberSerialization);
            if (type == typeof(TDerived) || type.IsSubclassOf(typeof(TDerived)))
            {
                foreach (var property in properties)
                {
                    if (!baseProperties.Contains(property.UnderlyingName))
                        property.Ignored = true;
                }
            }
            return properties;
        }
    }
    

    Then, cache an instance somewhere for performance as suggested by Newtonsoft:

    static IContractResolver elementAsElementWriterResolver = new UpcastingContractResolver<Element, IElementWriter>();
    

    And populate your element as follows:

    // Only populate those properties present in IElementWriter
    JsonConvert.PopulateObject(json, element, new JsonSerializerSettings
                               { 
                                   ContractResolver = elementAsElementWriterResolver 
                               });
    

    Demo fiddle here.

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