JSON.net: how to deserialize without using the default constructor?

后端 未结 5 1903
天涯浪人
天涯浪人 2020-11-22 07:21

I have a class that has a default constructor and also an overloaded constructor that takes in a set of parameters. These parameters match to fields on the object and are a

5条回答
  •  旧时难觅i
    2020-11-22 07:44

    Based on some of the answers here, I have written a CustomConstructorResolver for use in a current project, and I thought it might help somebody else.

    It supports the following resolution mechanisms, all configurable:

    • Select a single private constructor so you can define one private constructor without having to mark it with an attribute.
    • Select the most specific private constructor so you can have multiple overloads, still without having to use attributes.
    • Select the constructor marked with an attribute of a specific name - like the default resolver, but without a dependency on the Json.Net package because you need to reference Newtonsoft.Json.JsonConstructorAttribute.
    public class CustomConstructorResolver : DefaultContractResolver
    {
        public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
        public bool IgnoreAttributeConstructor { get; set; } = false;
        public bool IgnoreSinglePrivateConstructor { get; set; } = false;
        public bool IgnoreMostSpecificConstructor { get; set; } = false;
    
        protected override JsonObjectContract CreateObjectContract(Type objectType)
        {
            var contract = base.CreateObjectContract(objectType);
    
            // Use default contract for non-object types.
            if (objectType.IsPrimitive || objectType.IsEnum) return contract;
    
            // Look for constructor with attribute first, then single private, then most specific.
            var overrideConstructor = 
                   (this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType)) 
                ?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType)) 
                ?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));
    
            // Set override constructor if found, otherwise use default contract.
            if (overrideConstructor != null)
            {
                SetOverrideCreator(contract, overrideConstructor);
            }
    
            return contract;
        }
    
        private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
        {
            contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
            contract.CreatorParameters.Clear();
            foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
            {
                contract.CreatorParameters.Add(constructorParameter);
            }
        }
    
        private ObjectConstructor CreateParameterizedConstructor(MethodBase method)
        {
            var c = method as ConstructorInfo;
            if (c != null)
                return a => c.Invoke(a);
            return a => method.Invoke(null, a);
        }
    
        protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
        {
            var constructors = objectType
                .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();
    
            if (constructors.Count == 1) return constructors[0];
            if (constructors.Count > 1)
                throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");
    
            return null;
        }
    
        protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
        {
            var constructors = objectType
                .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
    
            return constructors.Length == 1 ? constructors[0] : null;
        }
    
        protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
        {
            var constructors = objectType
                .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .OrderBy(e => e.GetParameters().Length);
    
            var mostSpecific = constructors.LastOrDefault();
            return mostSpecific;
        }
    }
    
    
    

    Here is the complete version with XML documentation as a gist: https://gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

    Feedback appreciated.

    提交回复
    热议问题