【问题标题】:add custom code when specific type is deserialized反序列化特定类型时添加自定义代码
【发布时间】:2020-06-26 08:22:25
【问题描述】:

我想在每次对特定类进行反序列化时运行一些代码。 (我无法访问//修改类以添加[OnDeserialized] 属性)

我的第一种方法是使用自定义 jsonconverter 并在 readjson 中运行一些代码。 问题是,自定义类可能很复杂,并且反序列化所有子属性听起来太多(当所有子属性都在没有转换器的情况下正确反序列化时)。

有没有办法在反序列化类型时添加自定义代码? 或者如何使用 jsonconverter readjson 来创建和递归填充其内容(就像反序列化器在没有转换器的情况下所做的那样)?

还有一件事,类型是派生的,所以我不能使用 Jsonconverter.CanConvert 方法。 (因为它在基本类型上触发)

这只是我在readjson中使用的一些想法

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    // Load the JSON for the Result into a JObject
    JObject jObject = JObject.Load(reader);

    // Read the properties which will be used as constructor parameters
    //var enemy = Activator.CreateInstance(null, jObject["$type"]);

    object t = jObject["$type"];    
    string[] split = t.ToString().Split(',');
    string ass = split[1].Trim();
    string typ = split[0];

    var allassemblies = AppDomain.CurrentDomain.GetAssemblies();
    
    Assembly a = null;
    foreach (Assembly assembly in allassemblies)
    {
        if (assembly.FullName.StartsWith(ass))
        {
            a = assembly;
            break;
        }
    }

    Type tt = a.GetType(typ);

    var instance = Activator.CreateInstance(tt);
   
    //DO I REALLY NEED TO RECURSIVELY WORK THROUGH THE CONTENT (how to populate the instance?)

    //SOME CUSTOM CODE dependant on the deserialized type
    //before returning change working dir to the auc addons settings directory
    AddonUserControl auc = instance as AddonUserControl;
    dm_addon dma = AddonManager.FindAddonDataByAUCAssembly(auc);
    SystemExtensions.SetRelativeWorkingDirectory("settings//" + dma.Path);

    return instance;
}

请注意,我无法修改被反序列化的类型。

【问题讨论】:

标签: types json.net deserialization


【解决方案1】:

如果您无法修改类型以添加​​[OnDeserialized] callback,那么确保为每个AddonUserControl 调用自定义逻辑的最简单方法是在运行时使用custom contract resolver 添加回调。

首先,定义如下解析器:

public class ControlContractResolver : DefaultContractResolver
{
    static readonly SerializationCallback callback = (o, context) =>
        {
            if (o is AddonUserControl auc)
            {
                // Add your logic here:
                Debug.WriteLine("AddonUserControl's OnDeserializedCallback called for {0}", auc);
                dm_addon dma = AddonManager.FindAddonDataByAUCAssembly(auc);
                SystemExtensions.SetRelativeWorkingDirectory("settings//" + dma.Path);  
            }
        };
        
    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);
        if (typeof(AddonUserControl).IsAssignableFrom(objectType))
        {
            contract.OnDeserializedCallbacks.Add(callback);
        }
        return contract;
    }
}

然后在某处为performance分配和缓存一个静态实例:

static IContractResolver myResolver = new ControlContractResolver 
{
    // Set anything you might need here, e.g.
    //NamingStrategy = new CamelCaseNamingStrategy(),
}

并序列化如下:

var settings = new JsonSerializerSettings
{
    ContractResolver = myResolver,
    TypeNameHandling = TypeNameHandling.Auto, // Or TypeNameHandling.Objects or etc.
    // Other settings as required,
};
var control2 = JsonConvert.DeserializeObject<BaseControl>(json, settings);

确保对序列化和反序列化使用相同的设置。

演示小提琴here.

如果您仍想使用自定义JsonConverter,您可以按照How can I populate an existing object from a JToken (using Newtonsoft.Json)? 的回答中显示的说明从jObject 填充您预先分配的instance。但是请注意,您现有的转换器容易受到"Friday the 13th JSON Attacks" 的攻击,例如TypeNameHandling caution in Newtonsoft Json 中所示的转换器。 至少您必须检查实际类型是否可分配给预期类型:

if (!typeof(BaseControlType).IsAssignableFrom(tt))
    throw new JsonSerializationException(string.Format("Unexpected type {0}", tt));

无论您选择哪种方法,为了减少成功攻击的机会,您应该考虑创建一个 custom ISerializationBinder 来清理传入类型。如果您使用自定义合约解析器,序列化绑定器将被自动调用。如果您使用自定义转换器,则需要手动调用活页夹。为此,请参阅例如this answerHow to deserialize json objects into specific subclasses?

【讨论】:

  • 感谢您提供这么棒的答案/信息。我现在使用 serializer.populatevalues。顺便说一句,我需要这个来存储只有图像文件名作为源的 Imagebrushes,而不是指定 baseuri,因为它应该与图像(设置/主题)文件夹相关。当父对象被反序列化时,我必须指定工作目录,因此当反序列化器迭代子图像画笔时,可以找到文件。 (Directory.SetCurrentDirectory(..))
  • @l2rek - 不客气。如果这回答了你的问题,请mark it as such
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多