使用Newtonsoft.JSON库创建自定义JSON转换器以获取其他数据,而不会覆盖CanRead和CanWrite标志

时间:2017-09-19 06:59:44

标签: c# json asp.net-web-api serialization json.net

我目前正在开发一个用于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。我想为父实体和子实体捕获其他数据。附加数据不包含参考类型的数据。

1 个答案:

答案 0 :(得分:1)

可以使用线程静态变量或ThreadLocal<T>成员禁用转换器,如JSON.Net throws StackOverflowException when using JsonConvertGeneric 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原语 - stringboollong等等 - 而不是JValue个对象。只有在遇到值为JSON对象或数组的其他属性时才会将JToken添加到字典中,在这种情况下,您可以使用How do I use JSON.NET to deserialize into nested/recursive Dictionary and List?中的答案将JToken转换为传统的.Net类型。 (但是,您的问题表明附加数据不包含引用类型的数据,因此这不是必需的。)

以这种方式使用[JsonExtensionData]完全避免了对转换器的需求,同时还根据您的要求对基元进行反序列化,因此看起来比问题中显示的原始设计简单得多。

示例.Net fiddle证明扩展属性可以反序列化为AbcDTO,并断言它们都不属于JToken类型。