3

我实现了在运行时将“属性”添加到具有特殊 SystemComponent.PropertyDescriptor-s 的对象的可能性。

由于这些属性只能通过 ComponentModel.TypeDescriptor 而不是通过反射访问,因此这些属性在 WPF 环境中运行良好,但不适用于序列化。

这是因为我知道的所有 JSON 序列化程序都对类型使用反射。我分析了Newtonsoft.Json、System.Json、System.Web.Script.JavaScriptSerializer、System.Runtime.Serialization.Json。

我认为我不能使用这些序列化程序中的任何一个,因为它们都不允许修改实例上的属性的检索(例如,无法使用 ContractResolver)。

有什么方法可以使 JSON 序列化与这些序列化程序之一一起工作?也许通过特殊配置,覆盖序列化器或类似方法上的某些方法?是否有其他可用的序列化程序可以满足此要求?

背景:

运行时属性的想法基于此博客条目

序列化要求来自使用 dotNetify 序列化视图模型以将它们发送到客户端。

目前,我制作了一个 dotnetify 的分支,并通过使用 Newtonsoft.Json 和递归助手进行部分序列化来临时解决序列化问题。(如果对它感兴趣,可以查看差异:Fork)。

4

2 回答 2

4

一种可能性是创建一个自定义ContractResolver,当序列化特定类型的对象时TTarget,添加一个合成器,该合成ExtensionDataGetter器为指定的目标返回IEnumerable<KeyValuePair<Object, Object>>其对应的DynamicPropertyManager<TTarget>.

首先,定义合约解析器如下:

public class DynamicPropertyContractResolver<TTarget> : DefaultContractResolver
{
    readonly DynamicPropertyManager<TTarget> manager;
    readonly TTarget target;

    public DynamicPropertyContractResolver(DynamicPropertyManager<TTarget> manager, TTarget target)
    {
        if (manager == null)
            throw new ArgumentNullException();
        this.manager = manager;
        this.target = target;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        if (objectType == typeof(TTarget))
        {
            if (contract.ExtensionDataGetter != null || contract.ExtensionDataSetter != null)
                throw new JsonSerializationException(string.Format("Type {0} already has extension data.", typeof(TTarget)));
            contract.ExtensionDataGetter = (o) =>
                {
                    if (o == (object)target)
                    {
                        return manager.Properties.Select(p => new KeyValuePair<object, object>(p.Name, p.GetValue(o)));
                    }
                    return null;
                };
            contract.ExtensionDataSetter = (o, key, value) =>
                {
                    if (o == (object)target)
                    {
                        var property = manager.Properties.Where(p => string.Equals(p.Name, key, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
                        if (property != null)
                        {
                            if (value == null || value.GetType() == property.PropertyType)
                                property.SetValue(o, value);
                            else
                            {
                                var serializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = this });
                                property.SetValue(o, JToken.FromObject(value, serializer).ToObject(property.PropertyType, serializer));
                            }
                        }
                    }
                };
            contract.ExtensionDataValueType = typeof(object);
        }

        return contract;
    }
}

然后按如下方式序列化您的对象:

var obj = new object();

//Add prop to instance
int propVal = 0; 
var propManager = new DynamicPropertyManager<object>(obj);
propManager.Properties.Add(
    DynamicPropertyManager<object>.CreateProperty<object, int>(
    "Value", t => propVal, (t, y) => propVal = y, null));

propVal = 3;

var settings = new JsonSerializerSettings
{
    ContractResolver = new DynamicPropertyContractResolver<object>(propManager, obj),
};

//Serialize object here
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);

Console.WriteLine(json);

哪些输出,根据需要,

{"Value":3}

显然,这可以通过将动态属性管理器和目标的集合传递给增强的DynamicPropertyContractResolver<TTarget>. 只要合约解析器具有从被(反)序列化的目标映射到ExtensionDataGetter其.ExtensionDataSetterDynamicPropertyManager

限制:如果TTarget类型已经有扩展数据成员,这将不起作用。

于 2017-10-27T06:51:19.383 回答
4

感谢 dbc 的回答,我的解决方案是使用 System.ComponentModel.TypeDescriptor 的 ContractResolver

public class TypeDescriptorContractResolver : DefaultContractResolver
{

    public TypeDescriptorContractResolver()
    {
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);


        if (contract.ExtensionDataGetter != null || contract.ExtensionDataSetter != null)
            throw new JsonSerializationException(string.Format("Type {0} already has extension data.", objectType));

        contract.ExtensionDataGetter = (o) =>
        {
            return TypeDescriptor.GetProperties(o).OfType<PropertyDescriptor>().Select(p => new KeyValuePair<object, object>(p.Name, p.GetValue(o)));
        };

        contract.ExtensionDataSetter = (o, key, value) =>
        {
            var property = TypeDescriptor.GetProperties(o).OfType<PropertyDescriptor>().Where(p => string.Equals(p.Name, key, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
            if (property != null)
            {
                if (value == null || value.GetType() == property.PropertyType)
                    property.SetValue(o, value);
                else
                {
                    var serializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = this });
                    property.SetValue(o, JToken.FromObject(value, serializer).ToObject(property.PropertyType, serializer));
                }
            }
        };
        contract.ExtensionDataValueType = typeof(object);

        return contract;
    }
}

我已经发布了这个,因为它是一种更通用的方法,对 DynamicProperties 没有任何依赖关系

于 2017-10-29T18:21:30.707 回答