繁体   English   中英

如何将运行时添加的“属性”序列化为Json

[英]How to serialize runtime added “properties” to Json

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

由于这些属性只能通过ComponentModel.TypeDescriptor访问,而不能通过Reflection访问,因此这些属性在WPF环境中运行良好,但在序列化时无效。

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

我不认为我可以使用任何这些序列化程序,因为这些都不允许修改实例上属性的检索(例如,不能使用ContractResolver)。

有没有办法让JSON序列化与其中一个序列化器一起工作? 也许通过特殊配置,覆盖Serializer或类似的某些方法? 是否有其他可用的序列化器满足此要求?

背景:

运行时属性的概念基于此博客条目

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

目前,我做了一个dotntify的分支,并通过使用Newtonsoft.Json和递归助手进行部分序列化为序列化做了一个临时的解决方法。 (如果对它感兴趣,你可以看看差异: 叉子 )。

一种可能性是创建一个自定义ContractResolver ,在序列化TTarget类型的特定对象时,添加一个合成的ExtensionDataGetter ,它为指定的目标返回其相应DynamicPropertyManager<TTarget>指定的属性的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 (以及用于反序列化的ExtensionDataSetter )的基本思想可以工作,只要合同解析器具有从(de)序列化的目标到其DynamicPropertyManager映射的某种机制。

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

感谢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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM