How to deserialize class without calling a constructor?

孤街醉人 提交于 2019-12-18 14:14:54

问题


I'm using Json.NET in my WCF data service.

Here's my class (simplified):

[DataContract]
public class Component
{
    public Component()
    {
        // I'm doing some magic here.
    }
}

How can I deserialize that class without invoking a constructor using JsonConvert.DeserializeObject?

Sorry if not clear, feel free to ask questions.


回答1:


  1. You could create a class that inherits from CustomCreationConverter and use FormatterServices.GetSafeUninitializedObject to create your object. It skips calling the constructor.

    More about CustomCreationConverter here.

  2. Placing [JsonObject(MemberSerialization.Fields)] on a class will make Json.NET use FormatterServices.GetSafeUninitializedObject by default (although Fields mode will also serialize public/private fields rather than public properties which you may not want).

  3. Move the logic you don't want run outside of the default constructor.




回答2:


A constructor is always invoked. I usually have two constructors. One for serialization (the default constructor) and one for all "regular" code:

[DataContract]
public class Component
{
    // for JSON.NET
    protected Component()
    {
    }

    public Component(allMandatoryFieldsHere)
    {
        // I'm doing some magic here.
    }
}

In that way I can also make sure that the dev specify all information which are required.

However, I do not really recommend that you use anything but DTO's when transfering information since it's otherwise possible to circumvent the encapsulation of your objects (anyone could initialize any field with any value). Well. If you use anything but anemic models.

Using FormatterServices.GetSafeUninitializedObject is imho therefore an ugly workaround, since no one can tell that you create all objects in an unintialized way. Constructor initialization is there for a reason. It's better that the classes can tell that it's OK to not call the real constructor by providing a "serialization" constructor as I suggested.




回答3:


Others already mentioned the second constructor, but using 2 attributes: [JsonConstructor] and [Obsolete] you can do much better than leaving it up to humans to remember which one to call.

    public ChatMessage()
    {   
        MessageID = ApplicationState.GetNextChatMessageID(); // An expensive call that uses up an otherwise free ID from a limited set and does disk access in the process.
    }


    [JsonConstructor] // This forces JsonSerializer to call it instead of the default.
    [Obsolete("Call the default constructor. This is only for JSONserializer", true)] // To make sure that calling this from your code directly will generate a compiler error. JSONserializer can still call it because it does it via reflection.
    public ChatMessage(bool DO_NOT_CALL_THIS)
    {
    }

[JsonConstructor] forces JsonSerializer to call it instead of the default.
[Obsolete("...", true)] Makes sure that calling this from your code directly will generate a compiler error. JSONserializer can still call it because it does it via reflection.




回答4:


The best option to avoid constructor calls on deserialization is to create special contract resolver that overrides creator function for all classes without constructor marked with JsonConstructor attribute. This way you still can force JSON.NET to call constructor if you really need it, but all other classes will be created much like in standard DataContract serializers in .NET. Here is the code:

/// <summary>
/// Special contract resolver to create objects bypassing constructor call.
/// </summary>
public class NoConstructorCreationContractResolver : DefaultContractResolver
{
    /// <summary>
    /// Creates a <see cref="T:Newtonsoft.Json.Serialization.JsonObjectContract"/> for the given type.
    /// </summary>
    /// <param name="objectType">Type of the object.</param>
    /// <returns>
    /// A <see cref="T:Newtonsoft.Json.Serialization.JsonObjectContract"/> for the given type.
    /// </returns>
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        // prepare contract using default resolver
        var objectContract = base.CreateObjectContract(objectType);

        // if type has constructor marked with JsonConstructor attribute or can't be instantiated, return default contract
        if (objectContract.OverrideConstructor != null || objectContract.CreatedType.IsInterface || objectContract.CreatedType.IsAbstract)
            return objectContract;

        // prepare function to check that specified constructor parameter corresponds to non writable property on a type
        Func<JsonProperty, bool> isParameterForNonWritableProperty =
            parameter =>
            {
                var propertyForParameter = objectContract.Properties.FirstOrDefault(property => property.PropertyName == parameter.PropertyName);

                if (propertyForParameter == null)
                    return false;

                return !propertyForParameter.Writable;
            };                  

        // if type has parameterized constructor and any of constructor parameters corresponds to non writable property, return default contract
        // this is needed to handle special cases for types that can be initialized only via constructor, i.e. Tuple<>
        if (objectContract.ParametrizedConstructor != null
            && objectContract.ConstructorParameters.Any(parameter => isParameterForNonWritableProperty(parameter)))
            return objectContract;

        // override default creation method to create object without constructor call
        objectContract.DefaultCreatorNonPublic = false;
        objectContract.DefaultCreator = () => FormatterServices.GetSafeUninitializedObject(objectContract.CreatedType);

        return objectContract;
    }
}

All you need is simply set this contract resolver in serializer settings before deserialization.



来源:https://stackoverflow.com/questions/13947997/how-to-deserialize-class-without-calling-a-constructor

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