我在WCF数据服务中使用Json.NET。
这是我的课程(简化版):
[DataContract]
public class Component
{
public Component()
{
// I'm doing some magic here.
}
}
如何在不使用JsonConvert.DeserializeObject
调用构造函数的情况下反序列化该类?
抱歉,如果不清楚,请随时提问。
[DataContract]
public class Component
{
// for JSON.NET
protected Component()
{
}
public Component(allMandatoryFieldsHere)
{
// I'm doing some magic here.
}
}
通过这种方式,我还可以确保dev指定所需的所有信息。
然而,我并不建议您在传输信息时使用DTO之外的任何东西,因为在其他方面可以绕过对象的封装(任何人都可以用任何值初始化任何字段)。好如果你用的不是贫血模型。
因此,使用FormatterServices.GetSafeUninitializedObject
是一种丑陋的解决方法,因为没有人能告诉你以非序列化的方式创建所有对象。构造函数初始化是有原因的。更好的是,类可以通过提供我建议的"序列化"构造函数来表明不调用真正的构造函数是可以的。
您可以创建一个继承自
CustomCreationConverter
的类并使用FormatterServices.GetSafeUninitializedObject
创建对象它跳过调用构造函数。有关CustomCreationConverter的更多信息,请点击此处。
浇筑类上的
[JsonObject(MemberSerialization.Fields)]
将使Json.NET使用默认情况下为FormatterServices.GetSafeUninitializedObject
(尽管字段模式还将序列化公共/私有字段,而不是您可能不想要的公共属性)。将不希望运行的逻辑移到默认构造函数之外。
其他人已经提到了第二个构造函数,但使用两个属性:[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调用它,而不是默认值
[Obsolete("…",true)]确保直接从代码中调用它将生成编译器错误。JSONserializer仍然可以调用它,因为它是通过反射实现的。
避免在反序列化时调用构造函数的最佳选项是创建特殊的约定解析器,该解析器覆盖所有没有标记JsonConstructor属性的构造函数的类的创建者函数。这样,如果您真的需要,您仍然可以强制JSON.NET调用构造函数,但所有其他类都将像.NET中的标准DataContract序列化程序一样创建
/// <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.OverrideCreator != 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.CreatorParameters.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;
}
}
您所需要的只是在反序列化之前,在序列化程序设置中设置此协定解析程序。
更新只读字段并只获取属性
如果只读字段应用了JsonProperty属性,则可以对其进行反序列化。如果get-only属性的后台字段应用了JsonProperty属性,则也可以反序列化该属性。从C#7.3开始,可以将属性应用于编译器生成的自动属性的后台字段。
利用这一点,我们可以做这样的事情:
public class SampleClass
{
[JsonProperty("Field1")]
private readonly string _field1 = "Field1Value";
[JsonProperty("Property1")]
private string _property1BackingField = "Property1Value";
[JsonIgnore]
public string Property1 => _property1BackingField;
[field: JsonProperty("Property2")]
[JsonIgnore]
public string Property2 { get; } = "Property2Value";
}
const string json = "{"Field1":"NEW-Field1Value","Property1":"NEW-Property1Value","Property2":"NEW-Property2Value"}";
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
// this settings is needed to work with compiler-generated backing fields
SerializeCompilerGeneratedMembers = true
}
};
var deserializedSample = JsonConvert.DeserializeObject<SampleClass>(json, serializerSettings);
如果你需要在旧的C#版本中这样做,或者对属性没有控制权,你可以用这样的东西覆盖DefaultContractResolver的CreateProperty()方法:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var jsonProperty = base.CreateProperty(member, memberSerialization);
if (jsonProperty.Writable == false)
{
// this uses compiler implementation details and may not work for all cases
// better to use smarter approach like in BackingFieldResolver from Mono.Reflection library
var fieldInfo = jsonProperty.DeclaringType.GetField($"<{jsonProperty.PropertyName}>k__BackingField",
BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
jsonProperty.ValueProvider = new ReflectionValueProvider(fieldInfo);
jsonProperty.Writable = true;
}
}
return jsonProperty;
}