How to apply ObjectCreationHandling.Replace to selected properties when deserializing JSON?

You have a few alternatives to force your list to be replaced rather than reused:

  1. You can add an attribute to the list property indicating that it should be replaced not reused:

    public class Configuration
    {
        [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public List<Tuple<int, int, int>> MyThreeTuple { get; set; }
    }
    
  2. You can use an array instead of a list, as arrays are always replaced. This might make sense if your list should always contain three items and is never resized:

    public class Configuration
    {
        public Tuple<int, int, int>[] MyThreeTuple { get; set; }
    
        public Configuration()
        {
            MyThreeTuple = new[]
            {
                new Tuple<int, int, int>(-100, 20, 501),
                new Tuple<int, int, int>(100, 20, 864),
                new Tuple<int, int, int>(500, 20, 1286),
            };
        }
    }
    
  3. If you don’t want your class definitions to have a dependency on Json.NET, you can make a custom JsonConverter that clears the list when deserializing:

    public class ConfigurationConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(Configuration).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var config = (existingValue as Configuration ?? (Configuration)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
            if (config.MyThreeTuple != null)
                config.MyThreeTuple.Clear();
            serializer.Populate(reader, config);
            return config;
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    Then use it with the following JsonSerializerSettings:

    var settings = new JsonSerializerSettings { Converters = new JsonConverter[] { new ConfigurationConverter() } };
    
  4. If you want all list properties to be replaced rather than reused, you can make a custom ContractResolver that does this:

    public class ListReplacementContractResolver : DefaultContractResolver
    {
        // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
        // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
        // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
        // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
        static readonly ListReplacementContractResolver instance;
    
        // Using a static constructor enables fairly lazy initialization.  http://csharpindepth.com/Articles/General/Singleton.aspx
        static ListReplacementContractResolver() { instance = new ListReplacementContractResolver(); }
    
        public static ListReplacementContractResolver Instance { get { return instance; } }
    
        protected ListReplacementContractResolver() : base() { }
    
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var jsonProperty = base.CreateProperty(member, memberSerialization);
            if (jsonProperty.ObjectCreationHandling == null && jsonProperty.PropertyType.GetListType() != null)
                jsonProperty.ObjectCreationHandling = ObjectCreationHandling.Replace;
            return jsonProperty;
        }
    }
    
    public static class TypeExtensions
    {
        public static Type GetListType(this Type type)
        {
            while (type != null)
            {
                if (type.IsGenericType)
                {
                    var genType = type.GetGenericTypeDefinition();
                    if (genType == typeof(List<>))
                        return type.GetGenericArguments()[0];
                }
                type = type.BaseType;
            }
            return null;
        }
    }
    

    Then use it with the following settings:

    var settings = new JsonSerializerSettings { ContractResolver = ListReplacementContractResolver.Instance };
    
  5. If the collection is get-only (which it is not in this case) see Clear collections before adding items when populating existing objects.

Leave a Comment

tech