zoukankan      html  css  js  c++  java
  • Newtonsoft.Json 序列化踩坑之 IEnumerable

    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

    ContractResolver.CreateContract

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

    Reference

  • 相关阅读:
    Android ImageView 的scaleType 属性图解
    Android 颜色透明度换算
    Android动态控制状态栏显示和隐藏
    xutils Error:(37, 39) 错误: 无法访问HttpRequestBase 找不到org.apache.http.client.methods.HttpRequestBase的类文件
    Android学习笔记————利用JDBC连接服务器数据库
    android权限(permission)大全
    as无法在vivo上安装程序解决
    Android Menu用法全面讲解
    Android Studio gradle配置详解
    各种面试题(二)
  • 原文地址:https://www.cnblogs.com/weihanli/p/11081483.html
Copyright © 2011-2022 走看看