使用 JsonConverter 进行 OnDeserialized 回调

2024-02-11

我正在尝试使用JsonConverter from 这个答案 https://stackoverflow.com/a/33094930/2120779 to 我可以在属性中指定路径以将类中的属性映射到 JSON 中的子属性吗? https://stackoverflow.com/q/33088462/3744182 by 布赖恩·罗杰斯 https://stackoverflow.com/users/10263/brian-rogers将 JSON 中的嵌套属性映射到平面对象。

转换器运行良好,但我需要启动OnDeserialized回调来填充其他属性,但它不会被触发。如果我不使用转换器,则会触发回调。

例子:

string json = @"{
    'response': {
        'code': '000',
        'description': 'Response success',
    },
    'employee': {
        'name': 'Test',
        'surname': 'Testing',
        'work': 'At office'
    }
}";
// employee.cs

public class EmployeeStackoverflow
{
    [JsonProperty("response.code")]
    public string CodeResponse { get; set; }

    [JsonProperty("employee.name")]
    public string Name { get; set; }

    [JsonProperty("employee.surname")]
    public string Surname { get; set; }

    [JsonProperty("employee.work")]
    public string Workplace { get; set; }

    [OnDeserialized]
    internal void OnDeserializedMethod(StreamingContext context)
    {
        Workplace = "At Home!!";
    }
}
// employeeConverter.cs
public class EmployeeConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType,
                                object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        object targetObj = Activator.CreateInstance(objectType);

        foreach (PropertyInfo prop in objectType.GetProperties()
                                                .Where(p => p.CanRead && p.CanWrite))
        {
            JsonPropertyAttribute att = prop.GetCustomAttributes(true)
                                            .OfType<JsonPropertyAttribute>()
                                            .FirstOrDefault();

            string jsonPath = (att != null ? att.PropertyName : prop.Name);
            JToken token = jo.SelectToken(jsonPath);

            if (token != null && token.Type != JTokenType.Null)
            {
                object value = token.ToObject(prop.PropertyType, serializer);
                prop.SetValue(targetObj, value, null);
            }
        }

        return targetObj;
    }

    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when [JsonConverter] attribute is used
        return false;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value,
                                    JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

}

如果我添加[JsonConverter(typeof(EmployeeConverter))]在 Employee 类中我得到:

=== With Converter ===
Code: 000
Name: Test
Surname: Testing
Workplace: At office

如果我删除[JsonConverter(typeof(EmployeeConverter))]从 Employee 类我得到:

=== With Converter ===
Code:
Name:
Surname:
Workplace: At Home!!

我的目标是获得:

=== With Converter ===
Code: 000
Name: Test
Surname: Testing
Workplace: At Home!!

转换器是否缺少某些东西?


一旦你创建了一个custom JsonConverter https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm对于一种类型,转换器有责任处理一切这需要在反序列化期间完成——包括

  • Calling 序列化回调 https://www.newtonsoft.com/json/help/html/SerializationCallbacks.htm.
  • 跳过被忽略的属性。
  • 调用JsonConverter.ReadJson()用于通过属性附加到类型成员的转换器。
  • 设置默认值、跳过空值、解析引用等。

完整逻辑可见JsonSerializerInternalReader.PopulateObject() https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L2320,理论上你可能需要让你的ReadJson()方法重复此方法。 (但实际上,您可能只会实现一小部分必要的逻辑子集。)

使此任务变得更容易的一种方法是使用 Json.NET 自己的JsonObjectContract https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_JsonObjectContract.htm类型元数据,如返回JsonSerializer.ContractResolver.ResolveContract(objectType) https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonSerializer_ContractResolver.htm。该信息包含序列化回调列表和JsonpropertyAttributeJson.NET 在反序列化期间使用的属性数据。使用此信息的转换器的修改版本如下:

// Modified from this answer https://stackoverflow.com/a/33094930
// To https://stackoverflow.com/questions/33088462/can-i-specify-a-path-in-an-attribute-to-map-a-property-in-my-class-to-a-child-pr/
// By https://stackoverflow.com/users/10263/brian-rogers
// By adding handling of deserialization callbacks and some JsonProperty attributes.
public override object ReadJson(JsonReader reader, Type objectType,
                            object existingValue, JsonSerializer serializer)
{
    var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract ?? throw new JsonException(string.Format("{0} is not a JSON object", objectType));

    var jo = JToken.Load(reader);
    if (jo.Type == JTokenType.Null)
        return null;
    else if (jo.Type != JTokenType.Object)
        throw new JsonSerializationException(string.Format("Unexpected token {0}", jo.Type));

    var targetObj = contract.DefaultCreator();
    
    // Handle deserialization callbacks
    foreach (var callback in contract.OnDeserializingCallbacks)
        callback(targetObj, serializer.Context);

    foreach (var property in contract.Properties)
    {
        // Check that property isn't ignored, and can be deserialized.
        if (property.Ignored || !property.Writable)
            continue;
        if (property.ShouldDeserialize != null && !property.ShouldDeserialize(targetObj))
            continue;
        var jsonPath = property.PropertyName;
        var token = jo.SelectToken(jsonPath);
        // TODO: default values, skipping nulls, PreserveReferencesHandling, ReferenceLoopHandling, ...
        if (token != null && token.Type != JTokenType.Null)
        {
            object value;
            // Call the property's converter if present, otherwise deserialize directly.
            if (property.Converter != null && property.Converter.CanRead)
            {
                using (var subReader = token.CreateReader())
                {
                    if (subReader.TokenType == JsonToken.None)
                        subReader.Read();
                    value = property.Converter.ReadJson(subReader, property.PropertyType, property.ValueProvider.GetValue(targetObj), serializer);
                }
            }
            // TODO: property.ItemConverter != null
            else
            {
                value = token.ToObject(property.PropertyType, serializer);
            }
            property.ValueProvider.SetValue(targetObj, value);
        }
    }
    
    // Handle deserialization callbacks
    foreach (var callback in contract.OnDeserializedCallbacks)
        callback(targetObj, serializer.Context);
        
    return targetObj;
}

演示小提琴here https://dotnetfiddle.net/hAP8FZ.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 JsonConverter 进行 OnDeserialized 回调 的相关文章

随机推荐