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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM