Netsoft Json 高级用法

前言

手机端应用讲究速度快,体验好。刚好手头上的一个项目服务端接口有性能问题,需要进行优化。在接口多次修改中,实体添加了很多字段用于中间计算或者存储,然后最终用Newtonsoft.Json进行序列化返回数据,经过分析一个简单的列表接口每一行数据返回了16个字段,但是手机APP端只用到了其中7个字段,剩余9个字段的数据全部都是多余的,如果接口返回数据为40K大小,也就是说大约20K的数据为无效数据,3G网络下20K下载差不多需要1s,不返回无效数据至少可以节约1s的时间,大大提高用户体验。本篇将为大家介绍Newtonsoft.Json的一些高级用法,可以修改很少的代码解决上述问题。

Newtonsoft.Json介绍

在做开发的时候,很多数据交换都是以json格式传输的。而使用Json的时候,我们很多时候会涉及到几个序列化对象的使用:DataContractJsonSerializer,JavaScriptSerializer 和 Json.NET即Newtonsoft.Json。大多数人都会选择性能以及通用性较好Json.NET,这个不是微软的类库,但是一个开源的世界级的Json操作类库,从下面的性能对比就可以看到它的其中之一的性能优点。
性能对比图

点击进入Json.NET 文档

Json.Net页面截图

基本用法

Json.Net是支持序列化和反序列化DataTable,DataSet,Entity Framework和Entity的。下面分别举例说明序列化和反序列化。

序列化DataTable

DataTable dt = new DataTable();
dt.Columns.Add("Age", Type.GetType("System.Int32"));
dt.Columns.Add("Name", Type.GetType("System.String"));
dt.Columns.Add("Sex", Type.GetType("System.String"));
dt.Columns.Add("IsMarry", Type.GetType("System.Boolean"));
for (int i = 0; i < 4; i++)
{
    DataRow dr = dt.NewRow();
    dr["Age"] = i + 1;
    dr["Name"] = "Name" + i;
    dr["Sex"] = i % 2 == 0 ? "男" : "女";
    dr["IsMarry"] = i % 2 > 0 ? true : false;
    dt.Rows.Add(dr);
}
Console.WriteLine(JsonConvert.SerializeObject(dt));

序列化结果
序列化结果

反序列化

string json = JsonConvert.SerializeObject(dt);
dt=JsonConvert.DeserializeObject<DataTable>(json);
foreach (DataRow dr in dt.Rows)
{
  Console.WriteLine("{0}\t{1}\t{2}\t{3}\t", dr[0], dr[1], dr[2], dr[3]);
}

反序列化结果
这里写图片描述

高级用法

  1. 忽略某些属性
  2. 默认值的处理
  3. 空值的处理
  4. 支持非公共成员
  5. 日期处理
  6. 自定义序列化的字段名称
  7. 动态决定属性是否序列化
  8. 枚举值的自定义格式化问题
  9. 自定义类型转换
  10. 全局序列化设置

一、忽略某些属性

  类似本问开头介绍的接口优化,实体中有些属性不需要序列化返回,可以使用该特性。首先介绍Json.Net序列化的模式:OptOutOptIn
OptOut : 默认值,类中所有公有成员会被序列化,如果不想被序列化,可以用特性 JsonIgnore
OptIn : 默认情况下,所有的成员不会被序列化,类中的成员只有标有特性 JsonProperty 的才会被序列化,当类的成员很多,但客户端仅仅需要一部分数据时,很有用

仅序列化姓名属性

[JsonObject(MemberSerialization.OptIn)]
public class Person
{
    public int Age { get; set; }

    [JsonProperty]
    public string Name { get; set; }

    public string Sex { get; set; }

    public bool IsMarry { get; set; }

    public DateTime Birthday { get; set; }
}

仅序列化姓名属性结果
仅序列化姓名属性结果

不需要是否结婚属性

[JsonObject(MemberSerialization.OptOut)]
public class Person
{
    public int Age { get; set; }

    public string Name { get; set; }

    public string Sex { get; set; }

    [JsonIgnore]
    public bool IsMarry { get; set; }

    public DateTime Birthday { get; set; }
}

不需要是否结婚属性结果
不需要是否结婚属性结果

通过上面的例子可以看到,要实现不返回某些属性的需求很简单。
1.在实体类上加上[JsonObject(MemberSerialization.OptOut)]
2.在不需要返回的属性上加上 [JsonIgnore] 说明。

二、默认值的处理

序列化时想忽略默认值属性可以通过JsonSerializerSettings.DefaultValueHandling来确定,该值为枚举值
DefaultValueHandling.Ignore: 序列化和反序列化时,忽略默认值
DefaultValueHandling.Include:序列化和反序列化时,包含默认值

[DefaultValue(10)]
public int Age { get; set; }
Person p = new Person { 
    Age = 10, 
    Name = "张三丰", 
    Sex = "男", 
    IsMarry = false, 
    Birthday = new DateTime(1991, 1, 2) 
};
JsonSerializerSettings jsetting=new JsonSerializerSettings();
jsetting.DefaultValueHandling=DefaultValueHandling.Ignore;
Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

默认值处理结果
*默认值处理结果

三、空值的处理

序列化时需要忽略值为NULL的属性,可以通过JsonSerializerSettings.NullValueHandling来确定,另外通过JsonSerializerSettings设置属性是对序列化过程中所有属性生效的,想单独对某一个属性生效可以使用JsonProperty,下面将分别展示两个方式

1.JsonSerializerSettings

Person p = new Person { 
    Age = 10, 
    Name = "张三丰", 
    Sex = "男", 
    IsMarry = false, 
    Birthday = new DateTime(1991, 1, 2),
    room=null
};

JsonSerializerSettings jsetting=new JsonSerializerSettings();
jsetting.NullValueHandling = NullValueHandling.Ignore;
Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

空值处理结果

2.JsonProperty

通过JsonProperty属性设置的方法,可以实现某一属性特别处理的需求,如默认值处理,空值处理,自定义属性名处理,格式化处理。上面空值处理实现

JsonProperty

[JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
public Room room { get; set; }

四、支持非公共成员

序列化时默认都是处理公共成员,如果需要处理非公共成员,就要在该成员上加特性”JsonProperty

[JsonProperty]
private int Height { get; set; }

五、日期处理

对于Dateime类型日期的格式化就比较麻烦了,系统自带的会格式化成iso日期标准这里写图片描述,但是实际使用过程中大多数使用的可能是yyyy-MM-dd 或者yyyy-MM-dd HH:mm:ss两种格式的日期,解决办法是可以将DateTime类型改成string类型自己格式化好,然后在序列化。如果不想修改代码,可以采用下面方案实现


Json.Net提供了IsoDateTimeConverter日期转换这个类,可以通过JsnConverter实现相应的日期转换

[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime Birthday { get; set; }

但是IsoDateTimeConverter日期格式不是我们想要的,我们可以继承该类实现自己的日期

public class ChinaDateTimeConverter : DateTimeConverterBase
{
     private static IsoDateTimeConverter dtConverter = 
     new IsoDateTimeConverter { 
         DateTimeFormat = "yyyy-MM-dd"
     };

     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
     {
         return dtConverter.ReadJson(reader, objectType, existingValue, serializer);
     }

     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
     {
         dtConverter.WriteJson(writer, value, serializer);
     }
 }

自己实现了一个yyyy-MM-dd格式化转换类,可以看到只是初始化IsoDateTimeConverter时给的日期格式为yyyy-MM-dd即可,下面看下效果

[JsonConverter(typeof(ChinaDateTimeConverter))]
public DateTime Birthday { get; set; }

日期处理结果

六、自定义序列化的字段名称

实体中定义的属性名可能不是自己想要的名称,但是又不能更改实体定义,这个时候可以自定义序列化字段名称。

[JsonProperty(PropertyName = "CName")]
public string Name { get; set; }

七、动态决定属性是否序列化

根据某些场景,可能A场景输出A,B,C三个属性,B场景输出E,F属性。虽然实际中不一定存在这种需求,但是json.net依然可以支持该特性。继承默认的DefaultContractResolver类,传入需要输出的属性,重写修改了一下,大多数情况下应该是要排除的字段少于要保留的字段, 为了方便书写这里修改了构造函数加入retain表示props是需要保留的字段还是要排除的字段

public class LimitPropsContractResolver : DefaultContractResolver
{
string[] props = null;

bool retain;

/// <summary>
/// 构造函数
/// </summary>
/// <param name="props">传入的属性数组</param>
/// <param name="retain">true:表示props是需要保留的字段  false:表示props是要排除的字段</param>
public LimitPropsContractResolver(string[] props, bool retain=true)
{
    //指定要序列化属性的清单
    this.props = props;

    this.retain = retain;
}

protected override IList<JsonProperty> CreateProperties(Type type,MemberSerialization memberSerialization)
{
    IList<JsonProperty> list =
    base.CreateProperties(type, memberSerialization);
    //只保留清单有列出的属性
    return list.Where(p => {
        if (retain)
        {
            return props.Contains(p.PropertyName);
        }
        else
        {
            return !props.Contains(p.PropertyName);
        }      
    }).ToList();
}
public int Age { get; set; }
[JsonIgnore]
public bool IsMarry { get; set; }
public string Sex { get; set; }
JsonSerializerSettings jsetting=new JsonSerializerSettings();
jsetting.ContractResolver = new LimitPropsContractResolver(
    new string[] { "Age", "IsMarry" }
);
Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

使用自定义的解析类,只输出”Age”, “IsMarry”两个属性,看下最终结果.只输出了Age属性,为什么IsMarry属性没有输出呢,因为标注了JsonIgnore
这里写图片描述

看到上面的结果想要实现pc端序列化一部分,手机端序列化另一部分就很简单了吧,我们改下代码实现一下

string[] propNames = null;
if (p.Age > 10)
{
  propNames = new string[] { "Age", "IsMarry" };
}
else
{
    propNames = new string[] { "Age", "Sex" };
}
jsetting.ContractResolver = new LimitPropsContractResolver(propNames);
Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

八、枚举值的自定义格式化问题

默认情况下对于实体里面的枚举类型系统是格式化成改枚举对应的整型数值,那如果需要格式化成枚举对应的字符怎么处理呢?Newtonsoft.Json也帮我们想到了这点,下面看实例

 pubic enum NotifyType
  {
       Mail=0,
       SMS=1
   }
   public class TestEnmu
   {
           public NotifyType Type { get; set; }
   }
   JsonConvert.SerializeObject(new TestEnmu());

枚举值的自定义格式化结果
枚举值的自定义格式化结果


现在改造一下,输出”Type”:”Mail”

public class TestEnmu
{
     /// <summary>
     /// 消息发送类型
     /// </summary>
     [JsonConverter(typeof(StringEnumConverter))]
     public NotifyType Type { get; set; }
 }

其它的都不变,在Type属性上加上了JsonConverter(typeof(StringEnumConverter))表示将枚举值转换成对应的字符串,而StringEnumConverterNewtonsoft.Json内置的转换类型,最终输出结果
这里写图片描述

九、自定义类型转换

默认情况下对于实体里面的Boolean系统是格式化成true或者false,对于true转成”是” false转成”否”这种需求改怎么实现了?我们可以自定义类型转换实现该需求,下面看实例

public class BoolConvert : JsonConverter
    {
        private string[] arrBString { get; set; }

        public BoolConvert()
        {
            arrBString = "是,否".Split(',');
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="BooleanString">将bool值转换成的字符串值</param>
        public BoolConvert(string BooleanString)
        {
            if (string.IsNullOrEmpty(BooleanString))
            {
                throw new ArgumentNullException();
            }
            arrBString = BooleanString.Split(',');
            if (arrBString.Length != 2)
            {
                throw new ArgumentException("BooleanString格式不符合规定");
            }
        }


        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            bool isNullable = IsNullableType(objectType);
            Type t = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;

            if (reader.TokenType == JsonToken.Null)
            {
                if (!IsNullableType(objectType))
                {
                    throw new Exception(string.Format("不能转换null value to {0}.", objectType));
                }

                return null;
            }

            try
            {
                if (reader.TokenType == JsonToken.String)
                {
                    string boolText = reader.Value.ToString();
                    if (boolText.Equals(arrBString[0], StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                    else if (boolText.Equals(arrBString[1], StringComparison.OrdinalIgnoreCase))
                    {
                        return false;
                    }
                }

                if (reader.TokenType == JsonToken.Integer)
                {
                    //数值
                    return Convert.ToInt32(reader.Value) == 1;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Error converting value {0} to type '{1}'", reader.Value, objectType));
            }
            throw new Exception(string.Format("Unexpected token {0} when parsing enum", reader.TokenType));
        }

        /// <summary>
        /// 判断是否为Bool类型
        /// </summary>
        /// <param name="objectType">类型</param>
        /// <returns>为bool类型则可以进行转换</returns>
        public override bool CanConvert(Type objectType)
        {
            return true;
        }


        public bool IsNullableType(Type t)
        {
            if (t == null)
            {
                throw new ArgumentNullException("t");
            }
            return (t.BaseType.FullName=="System.ValueType" && t.GetGenericTypeDefinition() == typeof(Nullable<>));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }

            bool bValue = (bool)value;

            if (bValue)
            {
                writer.WriteValue(arrBString[0]);
            }
            else
            {
                writer.WriteValue(arrBString[1]);
            }
        }
    }

自定义了BoolConvert类型,继承自JsonConverter。构造函数参数BooleanString可以让我们自定义将true false转换成相应字符串。下面看实体里面怎么使用这个自定义转换类型

public class Person
{
    [JsonConverter(typeof(BoolConvert))]
    public bool IsMarry { get; set; }
}

这里写图片描述

相应的有什么个性化的转换需求,都可以使用自定义转换类型的方式实现。

十、全局序列化设置

文章开头提出了Null值字段怎么不返回的问题,相应的在高级用法也给出了相应的解决方案使用jsetting.NullValueHandling = NullValueHandling.Ignore; 来设置不返回空值。这样有个麻烦的地方,每个不想返回空值的序列化都需设置一下。可以对序列化设置一些默认值方式么?下面将解答

Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
   JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
   {
    //日期类型默认格式化处理
     setting.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
      setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";

    //空值处理
      setting.NullValueHandling = NullValueHandling.Ignore;

      //高级用法九中的Bool类型转换 设置
      setting.Converters.Add(new BoolConvert("是,否"));

      return setting;
   });

最后给出一个普通的通用类

using AjaxPro;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;

namespace Maticsoft.Common
{
    /// <summary>
    /// JSON 转换类 
    /// 开发者:未完待续
    /// 开发时间: 2016-10-22
    /// Email:weiwandaixuGie@outlook.com
    /// </summary>
    public class JSON
    {
        /// <summary>
        /// 将对象序列化为JSON格式
        /// </summary>
        /// <param name="o">对象</param>
        /// <returns></returns>
        public static string SerializeObject(object o)
        {
            return JsonConvert.SerializeObject(o);
        }

        /// <summary>
        /// 将对象序列化为JSON格式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static string SerializeObject<T>(T t) where T : class
        {
            return JsonConvert.SerializeObject(t);
        }

        /// <summary>
        /// 解析JSON字符串生成对象实体
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="json">json字符串</param>
        /// <returns>对象实体</returns>
        public static T DeserializeJsonToObject<T>(string json) where T : class
        {
            JsonSerializer serializer = new JsonSerializer();
            StringReader sr = new StringReader(json);
            object o = serializer.Deserialize(new JsonTextReader(sr), typeof(T));
            T t = o as T;
            return t;
        }

        /// <summary>
        /// 解析JSON数组生成对象实体集合
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="json">json数组字符串</param>
        /// <returns>对象实体集合</returns>
        public static List<T> DeserializeJsonToList<T>(string json) where T : class
        {
            JsonSerializer serializer = new JsonSerializer();
            StringReader sr = new StringReader(json);
            object o = serializer.Deserialize(new JsonTextReader(sr), typeof(List<T>));
            List<T> list = o as List<T>;
            return list;
        }

        /// <summary>
        /// 反序列化JSON到给定的匿名对象.
        /// </summary>
        /// <typeparam name="T">匿名对象类型</typeparam>
        /// <param name="json">json字符串</param>
        /// <param name="anonymousTypeObject">匿名对象</param>
        /// <returns>匿名对象</returns>
        public static T DeserializeAnonymousType<T>(string json, T anonymousTypeObject)
        {
            T t = JsonConvert.DeserializeAnonymousType(json, anonymousTypeObject);
            return t;
        }

    }
}

这样设置以后,以后使用序列化的地方就不需要单独设置了,个人最喜欢设置的是空值处理这一块。

原文章地址: https://www.cnblogs.com/yanweidie/p/4605212.html#_label1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值