【问题标题】:Intercept list population to assign values in deserialization拦截列表填充以在反序列化中分配值
【发布时间】:2017-03-11 02:20:20
【问题描述】:

我有一个递归类(树层次结构),它派生自一个列表,它具有子项及其本身,由 JSON.NET 中的反序列化填充。

TLDR 版本是我想在子类中填充一个变量,从父类,在它存在的这个类的每个级别上,而不使用来自 JSON.NET 的 $ref 变量(存储到 cookie 时节省空间) .

对于那些关注我昨天的问题的人来说,这可能看起来像是重复的,但事实并非如此。这是相同的代码,但老问题围绕着 JSON 中的 setter 没有被触发(已解决),而答案带来了这个问题(措辞更贴切)。

最初的调用是:

_Items = Cookies.Instance.GetJObject<CartItems>(COOKIE, jsonSetting);

调用者:

public T GetJObject<T>(string cookieName, JsonSerializerSettings jset = null)
    {
        string cookieContent = Get(cookieName);
        return JsonConvert.DeserializeObject<T>(cookieContent, jset);
    }

自定义转换器类如下:

public class JsonCartConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(CartItem).IsAssignableFrom(objectType);
    }

    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);

        var type = obj["t"] != null ? (CARTITEMTYPE)obj["t"].Value<int>() : CARTITEMTYPE.GENERIC;
        var item = CartItems.NewByType(type);
        serializer.Populate(obj.CreateReader(), item);
        return item;
    }


    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {

    }
}

生成的 JSON 如下:

[{"t":1,"i":1,"n":4,"r":false,"c":[{"t":5,"i":2,"n":4,"r":false,"c":[]}]},{"t":1,"i":3,"n":4,"r":false,"c":[{"t":5,"i":4,"n":4,"r":false,"c":[{"t":4,"i":6,"n":14,"r":false,"c":[]}]},{"t":1,"i":5,"n":15,"r":false,"c":[]}]}]

而我正在尝试做的,将与此类似:

public class CartItems : List<CartItem>{

public new CartItem Add(CartItem item)
    {
        item.Parent = this;
        base.Add(item);
        return item;
    }

所以问题在于我们已经确定反序列化不会从 List 调用标准的 Add/Insert 方法来填充列表。我们知道它是通过反射创建列表,但是如何创建呢?我可以在插入时拦截子类对父类的分配,以从父类中为其分配子类中的变量(即child.Parent = this)?

我尝试在 JSON.NET 源代码中四处寻找用于填充的集合中的方法(因为即使使用反射,它也必须通过调用方法调用来添加它们,对吗?)。除非...它正在做这样的事情:

CartItems items = new CartItems() { new GenericItem() { }, new GenericItem() { } };

编辑:CartItems 是从列表派生的类。它填充了多个 CartItem 实例。

【问题讨论】:

    标签: c# json.net


    【解决方案1】:

    您的问题是您试图子类化 List&lt;T&gt; 以在从列表中添加和删除项目时保持父/子关系,但是 List&lt;T&gt; 并非设计为以这种方式子类化,因为没有相关方法是虚拟的。相反,您通过 public new CartItem Add(CartItem item) 隐藏了 Add() 方法,但事实证明 Json.NET 没有调用此替换方法(并且没有理由假设它会调用)。

    相反,您应该使用专门为此目的设计的Collection&lt;T&gt;。来自docs

    Collection&lt;T&gt; 类提供受保护的方法,可用于在添加和删除项目、清除集合或设置现有项目的值时自定义其行为。

    因此,您的对象模型应如下所示:

    public class CartItems : Collection<CartItem>
    {
        public CartItems() : base() { }
    
        public CartItems(CartItem parent) : this()
        {
            this.Parent = parent;
        }
    
        public CartItem Parent { get; private set; }
    
        protected override void RemoveItem(int index)
        {
            CartItem oldItem = null;
            if (index >= 0 && index < Count)
            {
                oldItem = this[index];
            }
    
            base.RemoveItem(index);
        }
    
        protected override void InsertItem(int index, CartItem item)
        {
            base.InsertItem(index, item);
            if (item != null)
                item.Parent = this;
        }
    
        protected override void SetItem(int index, CartItem item)
        {
            CartItem oldItem = null;
            if (index >= 0 && index < Count)
            {
                oldItem = this[index];
            }
    
            base.SetItem(index, item);
    
            if (oldItem != null)
                oldItem.Parent = null;
    
            if (item != null)
                item.Parent = this;
        }
    
        protected override void ClearItems()
        {
            foreach (var item in this)
                if (item != null)
                    item.Parent = null;
            base.ClearItems();
        }
    }
    
    public class CartItem
    {
        public CartItem()
        {
            this.Children = new CartItems(this);
        }
    
        public int t { get; set; }
        public int i { get; set; }
        public int n { get; set; }
        public bool r { get; set; }
    
        [JsonProperty("c")]
        public CartItems Children { get; private set; }
    
        [JsonIgnore]
        public CartItems Parent { get; set; }
    }
    

    请注意,CartItemCartItems 都具有 Parent 属性。 CartItems.Parent 在其构造函数中赋值。 CartItem.Parent 由重写的 InsertItemSetItemRemoveItem 方法分配。

    示例fiddle

    另见Collection&lt;T&gt; versus List&lt;T&gt; what should you use on your interfaces?

    【讨论】:

    • 这确实是一件美丽的事情,我一直在拼命想弄清楚这个问题。如果我可以投票 100 倍,我会的。我不知道为什么我没有想出这个,或者我是否曾经有过。我一直从错误的角度攻击它。
    • @dudeinco - 如果需要,您还可以让 CartItemsObservableCollection&lt;CartItem&gt; 继承,并且覆盖将按原样编译和工作。
    • 最初,我实际上是在考虑这个问题,并且可能会这样做,但我错过了实施的目标......再次感谢。
    • 快速澄清问题:在 SetItem 上拉旧项目的目的是什么?如果被替换了,不应该在销毁旧项目时自动删除父引用吗? ClearItems 同样的问题...
    • 我想我明白了 - 您的工作假设孩子可能是一个独立的对象,而不是单独附加到父对象(仅由父对象引用)。明白了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-03
    • 1970-01-01
    • 1970-01-01
    • 2017-02-01
    • 1970-01-01
    • 2012-02-20
    • 2015-12-27
    相关资源
    最近更新 更多