欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Newtonsoft.Json 序列化踩坑之 IEnumerable

程序员文章站 2022-12-10 08:16:19
`Newtonsoft.Json` 是 .NET 下最受欢迎 JSON 操作库,使用起来也是非常方便,有时候也可能会不小心就踩坑了,这次就踩了一个,坑是这样的,如果要序列化的对象实现了 `IEnumerable` 接口,`Newtonsoft.Json` 就会认为这个对象是一个数组。。然后遍历这个对... ......

newtonsoft.json 序列化踩坑之 ienumerable

intro

newtonsoft.json 是 .net 下最受欢迎 json 操作库,使用起来也是非常方便,有时候也可能会不小心就踩坑了,这次就踩了一个,坑是这样的,如果要序列化的对象实现了 ienumerable 接口,newtonsoft.json 就会认为这个对象是一个数组。。然后遍历这个对象,输出其中的值,如果是一个自定义的类型而且还有其他属性,其他属性就会被忽略,序列化之后就会发生数据丢失。

问题代码

在我的公用类库 weihanli.common 有一个分页列表的model:

在 1.0.21及之前版本是这样定义的 源码

using system;
using system.collections;
using system.collections.generic;

namespace weihanli.common.models
{
    /// <summary>
    /// ipagedlistmodel
    /// </summary>
    /// <typeparam name="t">type</typeparam>
    public interface ipagedlistmodel<out t> : ireadonlylist<t>
    {
        /// <summary>
        /// data
        /// </summary>
        ireadonlylist<t> data { get; }

        /// <summary>
        /// pagenumber
        /// </summary>
        int pagenumber { get; }

        /// <summary>
        /// pagesize
        /// </summary>
        int pagesize { get; }

        /// <summary>
        /// totaldatacount
        /// </summary>
        int totalcount { get; set; }
    }

    /// <inheritdoc />
    /// <summary>
    /// 分页model
    /// </summary>
    /// <typeparam name="t">type</typeparam>
    [serializable]
    public class pagedlistmodel<t> : ipagedlistmodel<t>
    {
        public ireadonlylist<t> data { get; set; }

        private int _pagenumber = 1;

        public int pagenumber
        {
            get => _pagenumber;
            set
            {
                if (value > 0)
                {
                    _pagenumber = value;
                }
            }
        }

        private int _pagesize = 10;

        public int pagesize
        {
            get => _pagesize;
            set
            {
                if (value > 0)
                {
                    _pagesize = value;
                }
            }
        }

        private int _totalcount;

        public int totalcount
        {
            get => _totalcount;
            set
            {
                if (value > 0)
                {
                    _totalcount = value;
                }
            }
        }

        public int pagecount => convert.toint32(math.ceiling(_totalcount * 1.0 / _pagesize));

        public ienumerator<t> getenumerator()
        {
            return data.getenumerator();
        }

        ienumerator ienumerable.getenumerator()
        {
            return data.getenumerator();
        }

        public t this[int index] => data[index];

        public int count => data.count;
    }
}

上面的这种定义相当于实现了 ienumerable 接口,之所以实现这个接口,是因为可以直接遍历这个对象,不需要遍历这个对象的data 属性上遍历,但是这样序列化的时候就会有问题, pagenumber/pagesize/totalpage 之类的信息序列化时就会丢失

solution

不要实现 ienumerable 接口就可以了,修改后的代码如下所示:

using system;
using system.collections.generic;

namespace weihanli.common.models
{
    /// <summary>
    /// ipagedlistmodel
    /// </summary>
    /// <typeparam name="t">type</typeparam>
    public interface ipagedlistmodel<out t>
    {
        /// <summary>
        /// data
        /// </summary>
        ireadonlylist<t> data { get; }

        /// <summary>
        /// pagenumber
        /// </summary>
        int pagenumber { get; }

        /// <summary>
        /// pagesize
        /// </summary>
        int pagesize { get; }

        /// <summary>
        /// totaldatacount
        /// </summary>
        int totalcount { get; set; }
    }

    /// <inheritdoc />
    /// <summary>
    /// 分页model
    /// </summary>
    /// <typeparam name="t">type</typeparam>
    [serializable]
    public class pagedlistmodel<t> : ipagedlistmodel<t>
    {
        public ireadonlylist<t> data { get; set; }

        private int _pagenumber = 1;

        public int pagenumber
        {
            get => _pagenumber;
            set
            {
                if (value > 0)
                {
                    _pagenumber = value;
                }
            }
        }

        private int _pagesize = 10;

        public int pagesize
        {
            get => _pagesize;
            set
            {
                if (value > 0)
                {
                    _pagesize = value;
                }
            }
        }

        private int _totalcount;

        public int totalcount
        {
            get => _totalcount;
            set
            {
                if (value > 0)
                {
                    _totalcount = value;
                }
            }
        }

        public int pagecount => convert.toint32(math.ceiling(_totalcount * 1.0 / _pagesize));

        public t this[int index] => data[index];

        public int count => data.count;
    }
}

test

写个示例测试一下,原来的代码类型改为 pagedlistmodel1 ,测试代码如下:

pagedlistmodel1:

using system;
using system.collections;
using system.collections.generic;
using system.text;

namespace dotnetcoresample.test
{
    public class pagedlistmodel1<t> : ienumerable<t>
    {
        public ireadonlylist<t> data { get; set; }

        private int _pagenumber = 1;

        public int pagenumber
        {
            get => _pagenumber;
            set
            {
                if (value > 0)
                {
                    _pagenumber = value;
                }
            }
        }

        private int _pagesize = 10;

        public int pagesize
        {
            get => _pagesize;
            set
            {
                if (value > 0)
                {
                    _pagesize = value;
                }
            }
        }

        private int _totalcount;

        public int totalcount
        {
            get => _totalcount;
            set
            {
                if (value > 0)
                {
                    _totalcount = value;
                }
            }
        }

        public int pagecount => convert.toint32(math.ceiling(_totalcount * 1.0 / _pagesize));

        public t this[int index] => data[index];

        public int count => data.count;

        public ienumerator<t> getenumerator()
        {
            return data.getenumerator();
        }

        ienumerator ienumerable.getenumerator()
        {
            return data.getenumerator();
        }
    }
}

测试代码:

var pagedlistmodel = new pagedlistmodel<int>()
            {
                pagenumber = 2, pagesize = 2, totalcount = 6, data = new int[] {1, 2},
            };
var pagedlistmodel1 = new pagedlistmodel1<int>()
            {
                pagenumber = 2,
                pagesize = 2,
                totalcount = 6,
                data = new int[] { 1, 2 },
            };
console.writeline($"pagedlistmodel:{jsonconvert.serializeobject(pagedlistmodel)}, pagedlistmodel1:{jsonconvert.serializeobject(pagedlistmodel1)}");

output:

pagedlistmodel:{"data":[1,2],"pagenumber":2,"pagesize":2,"totalcount":6,"pagecount":3,"count":2}, pagedlistmodel1:[1,2]

可以看到实现了 ienumerable 接口的那个类序列化之后一些属性丢失了

research

查看 newtonsoft.json 源码 https://github.com/jamesnk/newtonsoft.json
,找到为什么实现了 ienumerable 接口就会有问题,最后找到了这里 https://github.com/jamesnk/newtonsoft.json/blob/master/src/newtonsoft.json/serialization/defaultcontractresolver.cs#l1218

Newtonsoft.Json 序列化踩坑之 IEnumerable

可以看到只要实现了 ienumerable 接口,就会被当作是一个json 数组,foreach 遍历其中的元素,其他属性就会被忽略掉了,这就是为什么上面我们实现了 ienumerable 接口的对象序列化之后发生属性丢失的原因。

reference