【问题标题】:Newtonsoft issue with IDynamicMetaObjectProvider implementing classes on deserializationIDynamicMetaObjectProvider 实现反序列化类的 Newtonsoft 问题
【发布时间】:2017-07-22 07:45:50
【问题描述】:

目前我偶然发现了 Newtonsofts Json 库的一个问题,这对我来说完全是个谜。

我有一些实现 IDynamicMetaObjectProvider 接口的类。将多个对象序列化为 json 没有问题,我从对象的每个实例中都得到了我期望的 json。

但是反序列化让我很头疼。从我目前观察到的情况来看,该库似乎正在缓存它无法找到的每个动态属性的值,并在应用程序运行时保留它。例如,我有以下三个 Json:

{ "PropA": "1" }
{ "PropA": "2", "PropB": "1" }
{ "PropA": "3", "PropB": "2", "PropC": "1" }

连续反序列化 Json 将为我提供以下 .NET 对象:

{ "PropA": "1" }
{ "PropA": "1", "PropB": "1" }
{ "PropA": "1", "PropB": "1", "PropC": "1" }

但是!如果我将目标类型从实现 IDynamicMetaObjectProvider 的目标类型更改为字典或简单的动态,反序列化的对象将具有正确设置的属性。

我的类有一个 index 属性,在 setter 上设置了一个断点,已经为 setter 提供了错误的值(所以我的类的实现没有问题)。

public abstract class DynamicModelObject : IDynamicMetaObjectProvider //, IPropertyIndexer //, IDictionary<String, Object>
    {
        [NotMapped]
        [JsonIgnore]
        internal Dictionary<String, Object> properties = new Dictionary<String, Object>();

        [IgnoreProperty]
        [JsonIgnore]
        public override Object this[String propertyName]
        {
            get
            {
                object val;

                if (properties.TryGetValue(propertyName, out val)) {
                    return val;
                }

                var prop = this.GetType().GetProperty(propertyName);
                if (prop != null && prop.CanRead) {
                    return prop.GetValue(this);
                }
                return null;
            }

            set
            {
                isDearty = true;
                var prop = this.GetType().GetProperty(propertyName);
                if (prop != null && prop.CanWrite) {
                    prop.SetValue(this, value);
                } else {
                    properties[propertyName] = value;
                }

                var val = value as String;
                if (value == null || (val != null && String.IsNullOrEmpty(val))) {
                    properties.Remove(propertyName);
                }   
            }
        }

        public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new DynamicDictionaryPropertyStore<DynamicModelObject>(parameter, this);
        }

        [IgnoreProperty]
        public IEnumerable<String> DynamicPropertyMemberNames
        {
            get
            {
                foreach (var key in properties.Keys) {
                    yield return key;
                }
            }
        }

        private List<String> staticProperties = null;

        [IgnoreProperty]
        private IEnumerable<String> StaticPropertyMemberNames
        {
            get
            {
                if (staticProperties == null) {
                    staticProperties = new List<String>();
                    foreach (var prop in this.GetType()
                        .GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty)) {
                        if (!Attribute.IsDefined(prop, typeof(IgnorePropertyAttribute)) && !Attribute.IsDefined(prop, typeof(ScriptIgnoreAttribute))) {
                            staticProperties.Add(prop.Name);
                            yield return prop.Name;
                        }
                    }
                } else {
                    foreach (var prop in staticProperties) {
                        yield return prop;
                    }
                }
            }
        }

        [IgnoreProperty]
        private IEnumerable<String> AllPropertyMemberNames
        {
            get
            {
                foreach (var prop in DynamicPropertyMemberNames.Concat(StaticPropertyMemberNames)) {
                    yield return prop;
                }
            }
        }

        private class DynamicDictionaryPropertyStore<T> : DynamicMetaObject where T : DynamicModelObject
        {
            private T target;

            internal DynamicDictionaryPropertyStore(System.Linq.Expressions.Expression parameter, T target)
                : base(parameter, BindingRestrictions.Empty, target)
            {
                this.target = target;
            }

            public override IEnumerable<string> GetDynamicMemberNames()
            {
                return target.DynamicPropertyMemberNames;
            }

            public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
            {
                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
                System.Linq.Expressions.Expression self = System.Linq.Expressions.Expression.Convert(Expression, LimitType);

                if (binder == null) return null;

                var body = System.Linq.Expressions.Expression.Property(self, "Item", System.Linq.Expressions.Expression.Constant(binder.Name));
                var convert = System.Linq.Expressions.Expression.Convert(System.Linq.Expressions.Expression.Constant(value.Value), typeof(object));
                var lambda = System.Linq.Expressions.Expression.Assign(body, convert);

                return new DynamicMetaObject(lambda, restrictions);
            }

            public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
            {
                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
                System.Linq.Expressions.Expression self = System.Linq.Expressions.Expression.Convert(Expression, LimitType);

                if (binder == null) return null;

                var body = System.Linq.Expressions.Expression.Property(self, "Item", System.Linq.Expressions.Expression.Constant(binder.Name));

                return new DynamicMetaObject(body, restrictions);
            }
        }
    }

怎么了??

【问题讨论】:

  • 你能提供你的实现 IDynamicMetaObjectProvider 的类吗?

标签: c# json dynamic json.net


【解决方案1】:

您对DynamicModelObject 的实现不正确。在DynamicDictionaryPropertyStore 子类中,您执行以下操作:

public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
    BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
    System.Linq.Expressions.Expression self = System.Linq.Expressions.Expression.Convert(Expression, LimitType);

    if (binder == null) return null;
    var body = System.Linq.Expressions.Expression.Property(self, "Item", System.Linq.Expressions.Expression.Constant(binder.Name));
    var convert = System.Linq.Expressions.Expression.Convert(System.Linq.Expressions.Expression.Constant(value.Value), typeof(object));
    var lambda = System.Linq.Expressions.Expression.Assign(body, convert);

    return new DynamicMetaObject(lambda, restrictions);
}

如果您查看得到的表达式,您会看到(例如PropA):

Convert($arg0).Item["PropA"] = Convert("1")

因此,作为一个设置器,您返回的表达式调用您的索引器并分配常量值 (1),而不管实际传递了什么值。此表达式稍后将用于PropA(缓存)的所有设置器。因此你的问题是:你所有的设置器都会忽略传递的值,并且总是会分配你第一次调用它们的值。要修复,请替换此行:

var convert = System.Linq.Expressions.Expression.Convert(System.Linq.Expressions.Expression.Constant(value.Value), typeof(object));

有了这个:

var convert = System.Linq.Expressions.Expression.Convert(value.Expression, typeof(object));

生成的 setter 表达式将是:

Convert($arg0).Item["PropA"] = Convert($arg1)

请注意,那里没有成本,只有参数。之后你的问题就迎刃而解了。

【讨论】:

  • 谢谢!它现在按预期工作。我永远不会在这个地方搜索,但看到你的回答完全可以理解为什么会发生这种情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-18
  • 2022-01-25
  • 2013-06-06
  • 1970-01-01
相关资源
最近更新 更多