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

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

Reference

Newtonsoft.Json 序列化踩坑之 IEnumerable的更多相关文章

  1. [C#][Newtonsoft.Json] Newtonsoft.Json 序列化时的一些其它用法

    Newtonsoft.Json 序列化时的一些其它用法 在进行序列化时我们一般会选择使用匿名类型 new { },或者添加一个新类(包含想输出的所有字段).但不可避免的会出现以下情形:如属性值隐藏(敏 ...

  2. C# 使用Newtonsoft.Json序列化自定义类型

    Json.Net是一个读写Json效率比较高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简单.通过Linq To JSON可以快速的读写Json,通过JsonSerializ ...

  3. c# 使用 Newtonsoft.Json 序列化json字符串以及,反序列化对象

    1. 序列化 对象 /** 使用 Newtonsoft.Json 序列化对象 **/ [WebMethod] public String getPersonInfos() { // 初始化数据 Lis ...

  4. Newtonsoft.Json 序列化和反序列化 以及时间格式 2 高级使用

    手机端应用讲究速度快,体验好.刚好手头上的一个项目服务端接口有性能问题,需要进行优化.在接口多次修改中,实体添加了很多字段用于中间计算或者存储,然后最终用Newtonsoft.Json进行序列化返回数 ...

  5. Newtonsoft.Json序列化日期时间去T的几种方式。

    原文地址:MVC web api 返回JSON的几种方式,Newtonsoft.Json序列化日期时间去T的几种方式. http://www.cnblogs.com/wuball/p/4231343. ...

  6. MVC web api 返回JSON的几种方式,Newtonsoft.Json序列化日期时间去T的几种方式。

    原文链接:https://www.muhanxue.com/essays/2015/01/8623699.html MVC web api 返回JSON的几种方式 1.在WebApiConfig的Re ...

  7. Newtonsoft.Json 序列化和反序列化 时间格式【转】

    1.JSON序列化 string JsonStr= JsonConvert.SerializeObject(Entity); eg:   A a=new A(); a.Name="Elain ...

  8. newtonsoft.json 序列化,反序列化

    public class Book { public string BookID { get; set; } public DateTime PublishDate { get; set; } pub ...

  9. Newtonsoft.Json 序列化和反序列化 时间格式

    From : http://www.cnblogs.com/litian/p/3870975.html 1.JSON序列化 string JsonStr= JsonConvert.SerializeO ...

随机推荐

  1. Spring Cloud Alibaba 实战(十一) - Spring Cloud认证授权

    欢迎关注全是干货的技术公众号:JavaEdge 本文主要内容: 如何实现用户认证与授权? 实现的三种方案,全部是通过画图的方式讲解.以及三种方案的对比 最后根据方案改造Gateway和扩展Feign ...

  2. SpringBoot系列之日志框架使用教程

    目录 1.SpringBoot日志级别 1).日志级别简介 2).默认日志级别 3).配置日志级别 4).日志分组设置 2.SpringBoot日志格式设置 1).默认格式原理简介 2).默认日志格式 ...

  3. 【构建之法教学项目】一个简单的基于C#的电子商务系统演练场景的代码示例

    电子商务平台,是一个历史悠久而又充满挑战的行业,他和社交一起成为中国互联网市场的两极.电子商务系统是一个非常复杂的系统,他实现了人与物.人与人的链接,同时也需要大量的技术来支撑,实现系统的高可用.这些 ...

  4. windows下cocos2d-x工程结构讲解

    这是我们新建好的工程,稍微解释一下我们开发windows的cocos应用所用到的几个文件夹的作用 Classes文件夹,存放游戏代码中的类的源码,当然我们放在别的地方也可以,只要配置好依赖关系就行了 ...

  5. Implement Dependent Reference Properties 实现从属引用属性 (XPO)

    In this lesson, you will learn how to implement properties whose values can depend on other properti ...

  6. SSM框架之SpringMVC(1)入门程序

    SpringMVC(1) 1.三层架构和MVC 1.1. 三层架构 咱们开发服务器端程序,一般都基于两种形式,一种C/S架构程序,一种B/S架构程序 使用Java语言基本上都是开发B/S架构的程序,B ...

  7. 【React Native】在原生和React Native间通信(RN调用原生)

    一.从React Native中调用原生方法(原生模块) 原生模块是JS中也可以使用的Objective-C类.一般来说这样的每一个模块的实例都是在每一次通过JS bridge通信时创建的.他们可以导 ...

  8. apk系统签名小技巧

    前言 对于经常和android系统打交道的攻城狮来说,给app打系统签名一定是日常操作啦.由于最近使用的比较多,特此总结一下,减少复制粘贴的操作,通过命令行来搞定. 简化前的操作 1.Android ...

  9. 如何确定UNDO_RETENTION参数的值以避免ORA-1555 (Doc ID 822411.1)

    How to Determine the Value Of UNDO_RETENTION Parameter to Avoid ORA-1555 (Doc ID 822411.1) APPLIES T ...

  10. Centos7系统Docker安装

    目录 前期说明 安装步骤 1.官网中文安装参考手册 2.确定你是CentOS7及以上版本 3.yum安装gcc相关 4.卸载旧版本 5.安装需要的软件包 6.设置stable镜像仓库 7.更新yum软 ...