How to serialize runtime added “properties” to Json

后端 未结 2 518
深忆病人
深忆病人 2021-01-14 18:26

I implemented the possibility to add \"properties\" at runtime to objects with special SystemComponent.PropertyDescriptor-s.

Due to the fact that these properties ar

相关标签:
2条回答
  • 2021-01-14 19:04

    Thanks to dbc's answer my solution is a ContractResolver that uses System.ComponentModel.TypeDescriptor

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

    I have posted this, because it is a more general approach without any Dependencies to the DynamicProperties

    0 讨论(0)
  • 2021-01-14 19:07

    One possibility would be to create a custom ContractResolver that, when serializing a specific object of type TTarget, adds a synthetic ExtensionDataGetter that returns, for the specified target, an IEnumerable<KeyValuePair<Object, Object>> of the properties specified in its corresponding DynamicPropertyManager<TTarget>.

    First, define the contract resolver as follows:

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

    Then serialize your object as follows:

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

    Which outputs, as required,

    {"Value":3}
    

    Obviously this could be extended to serializing a graph of objects with dynamic properties by passing a collection of dynamic property managers and targets to an enhanced DynamicPropertyContractResolver<TTarget>. The basic idea, of creating a synthetic ExtensionDataGetter (and ExtensionDataSetter for deserialization) can work as long as the contract resolver has some mechanism for mapping from a target being (de)serialized to its DynamicPropertyManager.

    Limitation: if the TTarget type already has an extension data member, this will not work.

    0 讨论(0)
提交回复
热议问题