How to serialize runtime added “properties” to Json

后端 未结 2 541
深忆病人
深忆病人 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: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> of the properties specified in its corresponding DynamicPropertyManager.

    First, define the contract resolver as follows:

    public class DynamicPropertyContractResolver : DefaultContractResolver
    {
        readonly DynamicPropertyManager manager;
        readonly TTarget target;
    
        public DynamicPropertyContractResolver(DynamicPropertyManager 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(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(obj);
    propManager.Properties.Add(
        DynamicPropertyManager.CreateProperty(
        "Value", t => propVal, (t, y) => propVal = y, null));
    
    propVal = 3;
    
    var settings = new JsonSerializerSettings
    {
        ContractResolver = new DynamicPropertyContractResolver(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. 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.

    提交回复
    热议问题