Как десериализовать класс без вызова конструктора? - программирование
Подтвердить что ты не робот

Как десериализовать класс без вызова конструктора?

Я использую Json.NET в своей службе данных WCF.

Здесь мой класс (упрощенный):

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

Как я могу десериализовать этот класс без вызова конструктора с помощью JsonConvert.DeserializeObject?

Извините, если не ясно, не стесняйтесь задавать вопросы.

4b9b3361

Ответ 1

  • Вы можете создать класс, который наследует от CustomCreationConverter и используйте FormatterServices.GetSafeUninitializedObject, чтобы создать объект. Он пропускает вызов конструктора.

    Подробнее о CustomCreationConverter здесь.

  • Размещение [JsonObject(MemberSerialization.Fields)] в классе будет использовать Json.NET FormatterServices.GetSafeUninitializedObject по умолчанию (хотя Режим полей также будет сериализовать публичные/частные поля, а не общедоступные свойства, которые вы, возможно, не захотите).

  • Переместите логику, которую вы не хотите запускать за пределами конструктора по умолчанию.

Ответ 2

Конструктор всегда вызывается. Обычно у меня есть два конструктора. Один для сериализации (конструктор по умолчанию) и один для всего "обычного" кода:

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

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

Таким образом, я также могу убедиться, что dev укажет всю необходимую информацию.

Однако я не рекомендую использовать что-либо, кроме DTO, при передаче информации, поскольку в противном случае можно обойти инкапсуляцию ваших объектов (любой может инициализировать любое поле с любым значением). Что ж. Если вы используете что-либо, кроме анемичных моделей.

Использование FormatterServices.GetSafeUninitializedObject - это, следовательно, уродливое обходное решение, так как никто не может сказать, что вы создаете все объекты неинтеллизированным образом. Инициализация конструктора существует по какой-то причине. Лучше, чтобы классы могли сказать, что это нормально, чтобы не вызвать реальный конструктор, предоставив конструктор "сериализации", как я предложил.

Ответ 3

Другие уже упомянули второй конструктор, но используя 2 атрибута: [JsonConstructor] и [Obsolete] вы можете сделать намного лучше, чем оставить его человеку, чтобы запомнить, какой из них нужно вызвать.

    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] заставляет JsonSerializer вызывать его вместо стандартного.
[Устаревший ( "...", true)] Удостоверяется, что вызов этого из вашего кода напрямую вызовет ошибку компилятора. JSONserializer все равно может вызвать его, потому что он делает это через отражение.

Ответ 4

Лучшим вариантом избежать конструкторских вызовов при десериализации является создание специального распознавателя контрактов, который переопределяет функцию-создатель для всех классов без конструктора, помеченного атрибутом JsonConstructor. Таким образом, вы все еще можете заставить JSON.NET вызвать конструктор, если он вам действительно нужен, но все остальные классы будут созданы так же, как в стандартных сериализаторах DataContract в .NET. Вот код:

/// <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;
    }
}

Все, что вам нужно, просто установите этот контрактный преобразователь в настройках сериализатора перед десериализацией.