我目前正在开发一个用于WebAPI项目的自定义JSON转换器。要求是 - 我有一个DTO对象具有一些属性。 API可以由多个客户端使用。根据客户端,除了DTO模型中已存在的属性之外,我的DTO实体中很少有可能有一些额外的数据。我需要创建一个自定义JSON转换器来序列化和反序列化这些数据。
//DTO
class AbcDTO
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public List<AdditionalProperty> AdditionalData { get; set; }
}
//AdditionalProperty class
class AdditionalProperty
{
public string Name { get; set; }
public object Value { get; set; }
}
//Request JSON Body
{
"Prop1": "Val1",
"Prop2": "Val2",
"AdditionalProp3": "Val3",
"AdditionalProp4": "Val4"
}
//After Deserialization the object should be as below
AbcDTO dto = {
Prop1 = "Val1",
Prop2 = "Val2",
AdditionalData = [
{ Name = "AdditionalProp3", Value = "Val3" },
{ Name = "AdditionalProp4", Value = "Val4" }]
}
//After Serialization of the above dto object the JSON should convert back to the Request JSON Body format
我们不想使用Newtonsoft.JSON提供的JsonExtensionData
属性,因为我们需要将该属性保留为Dictionary<string, JToken>
- 但我们并不想通过JToken
到以下层。
创建了一个自定义JSON转换器 -
class CustomJsonConverter : JsonConverter
{
bool _canWrite = true;
bool _canRead = true;
public override bool CanConvert(Type objectType)
{
return typeof(IEntity).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get
{
return _canWrite;
}
}
public override bool CanRead
{
get
{
return _canRead;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
PropertyInfo[] availablePropertyNames = objectType.GetProperties();
List<AdditionalProperties> additionalData = new List<AdditionalProperties>();
IEntity obj;
_canRead = false;
obj = (IEntity)jObject.ToObject(objectType);
_canRead = true;
IEnumerable<JProperty> properties = jObject.Properties();
foreach (JProperty prop in properties)
{
if (availablePropertyNames.Count(x => x.Name.Equals(prop.Name)) == 0)
{
AdditionalProperties addProp = new AdditionalProperties
{
Name = prop.Name,
Value = prop.Value.ToObject<object>(),
};
additionalData.Add(addProp);
}
}
obj.AdditionalData = additionalData;
return obj;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEntity obj = (IEntity)value;
List<AdditionalProperties> additionalData = obj.AdditionalData;
JObject jObj;
_canWrite = false;
jObj = (JObject)JToken.FromObject(obj);
_canWrite = true;
jObj.Remove("AdditionalData");
foreach (AdditionalProperties data in additionalData)
{
jObj.Add(data.Name, JToken.FromObject(data.Value));
}
jObj.WriteTo(writer);
}
}
WebAPI ContractResolver为每个实体创建1个JSON转换器。现在问题是_canRead和_canWrite不是线程安全的。需要使用它们来使用Newtonsoft提供的基本实现。如果我们不使用它们,ToObject和FromObject方法会再次在内部调用自定义转换器方法,从而导致无限递归。将它们与日志一起使用会降低性能。有没有什么办法可以使用Newtonsoft.JSON序列化/反序列化的基本实现来创建自定义转换器而不使用canRead和canWrite标志?
我也可以有引用类型子属性 - 比如Person包含Address。我想为父实体和子实体捕获其他数据。附加数据不包含参考类型的数据。
答案 0 :(得分:1)
可以使用线程静态变量或ThreadLocal<T>
成员禁用转换器,如JSON.Net throws StackOverflowException when using JsonConvert或Generic method of modifying JSON before being returned to client所示。但是,我想建议一种解决问题的简单方法。
你写道,我们不想使用Newtonsoft.JSON提供的JsonExtensionData属性,因为我们需要将属性保留为Dictionary,我们不想将JToken传递给下面的层。没有必要扩展数据字典具有类型JToken
的值。 扩展数据字典支持object
类型的值,例如:
class AbcDTO
{
public AbcDTO() { this.AdditionalData = new Dictionary<string, object>(); }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
[JsonExtensionData]
public Dictionary<string, object> AdditionalData { get; private set; }
}
当扩展数据字典的类型为Dictionary<string, object>
时,Json.NET会将JSON原始值反序列化为等效的.Net原语 - string
,bool
,long
等等 - 而不是JValue
个对象。只有在遇到值为JSON对象或数组的其他属性时才会将JToken
添加到字典中,在这种情况下,您可以使用How do I use JSON.NET to deserialize into nested/recursive Dictionary and List?中的答案将JToken
转换为传统的.Net类型。 (但是,您的问题表明附加数据不包含引用类型的数据,因此这不是必需的。)
以这种方式使用[JsonExtensionData]
完全避免了对转换器的需求,同时还根据您的要求对基元进行反序列化,因此看起来比问题中显示的原始设计简单得多。
示例.Net fiddle证明扩展属性可以反序列化为AbcDTO
,并断言它们都不属于JToken
类型。