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

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 和递归助手进行序列化的临时解决方案。(如果您对此感兴趣,可以查看差异:the Fork)。


{btsdaf} - dbc
@dbc 属性只能通过TypeDescriptor在对象上进行访问,无法从Type获取。稍后我将提供示例实现。很遗憾的是,没有CreateProperties(Object object, MemberSerialization memberSerialization)方法。解析器中的一切都依赖于Type。 - Felix Keil
@dbc 这里是 DynamicProperties 的 Github 仓库(最小类,我已经在此基础上构建了更多 Rx 相关的类),还有一个 Consoleapp 可以尝试序列化。 - Felix Keil
2个回答

4
一种可能的方法是创建一个自定义ContractResolver,在序列化特定类型TTarget的对象时,添加一个合成的ExtensionDataGetter,该Getter返回指定目标的对应DynamicPropertyManager<TTarget>中指定属性的IEnumerable<KeyValuePair<Object, Object>>
首先,按以下方式定义契约解析器:
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)的基本思想是,只要契约解析器具有从正在(反)序列化的目标映射到其DynamicPropertyManager的机制,就可以工作。

限制:如果TTarget类型已经具有扩展数据成员,则此方法将无法正常工作。


太棒了,谢谢!这让我朝着正确的方向前进了。ExtensionDataGetter和Setter再次作用于对象的范围内! :) 我已经制作了一个版本,只使用了System.ComponentModel.TypeDescriptor,并且没有依赖于DynamicProperties。它已经提交到仓库中了。 - Felix Keil

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;
    }
}

我发布这篇文章是因为它是一种更通用的方法,没有依赖于动态属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接