为我中华 發表於 2019-6-24 16:28:00

C# Newtonsoft.Json 高级用法

<p>  最近在做接口开发,对方团队开发了一个<code>Web API</code>的接口,传输数据的格式是<code>JSON</code>。当时看到这个东西,感觉很简单,也没想什么,没用多久就完成了我的功能,我完成的功能很简单,就是获取数据,然后把数据列表进行<code>JSON</code>序列化,然后再以<code>POST</code>方式调用对方<code>Web Api</code>的接口,将<code>JSON</code>的数据一起传递过去,我想的很简单,直接调用并返回结果就完成了。最后对方接口返回错误,提示从传递过去的<code>JSON</code>数据中的第一个字段开始就取不到值。</p>
<p>  郁闷,为什么呢?我的参数也是按着他们接口的规范写的,数据获取也没错,<code>JSON</code>格式化的数据好像也正常,对方团队的<code>Api</code>接口为什么提示取不到值。后来,静下心来,仔细看了看我的项目,应该没啥大问题。突然,好像感觉找到了原因,但是感觉不应该是这个问题,那就是我传递的<code>JSON</code>数据的<code>Key</code>值的名称采用的是<code>Pascal</code>命名法,就是每个单词的首字符都是大写的(<code>{“BoyFirend”:“xxx”,"HomeAddress":"xxxx"}</code>),而对方的<code>Key</code>的命名法是<code>Camel</code>命名法(比如:<code>{“boyFirend”:“xxx”,"homeAddress":"xxxx"}</code>)。之所以产生这个结果,因为<code>JSON</code>数据是直接通过<code>C#</code>的实体类序列化而来的,在<code>C#</code> 中的实体类的每个属性的命名就是采用的是<code>Pascal</code>命名方法。找到了原因,但是不太确定,然后一试,还真是这个原因。</p>
<p>  原因找到了,解决问题就简单了,对方团队写的<code>Api</code>接口个人感觉兼容不好,不应该对大小写敏感,让接口更通用一点才好,我建议对方改一下接口,对方好像不怎么同意,那我只能修改我的接口了。说到<code>JSON</code>序列化,我使用的是<code>Newtonsoft.Json</code>。</p>
<h2 id="一newtonsoftjson介绍">一、Newtonsoft.Json介绍</h2>
<p>  做<code>Web</code>开发的,没有接触过<code>JavaScript</code>的肯定很少,做前端开发,没有接触过<code>Ajax</code>的估计更不多了。现在的系统大多数是分布式系统,分布式系统就会涉及到数据的传输,<code>JSON</code>在数据传输和提交方面有着天生的优势。当我们使用<code>Json</code>的时候,很多时候会涉及到几个序列化对象的使用:<code>DataContractJsonSerializer</code>、<code>JavaScriptSerializer</code>&nbsp;和Json.NET即<code>Newtonsoft.Json</code>。大多数人都会选择性能以及通用性较好<code>Json.NET</code>,这个不是微软的类库,是一个开源的<code>Json</code>操作类库,速度比<code>DataContractJsonSerializer</code>快50%,比<code>JavaScriptSerializer</code>快250%,从下面的性能对比就可以看到它的性能优点。</p>
<p>  <img src="https://images0.cnblogs.com/i/8867/201404/282228152051648.png" alt="" loading="lazy"></p>
<p>  它的确很快,使用也很简单,它支持序列化的对象也很多,我刚开始以为只是支持以实体类为基础的序列化,其实还有很多,比如:数据表(DataTable)、数据集(DataSet)等,你了解越深就越喜欢它,也避免你在不知的情况下“重复造轮子”。</p>
<h2 id="二基本用法">二、基本用法</h2>
<p>  Json.Net是支持序列化和反序列化<code>DataTable,DataSet,Entity Framework</code>和<code>Entity</code>的。下面分别举例说明序列化和反序列化。</p>
<p><strong>DataTable:</strong></p>
<pre><code class="language-c#">//序列化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 &lt; 4; i++)
{
    DataRow dr = dt.NewRow();
    dr["Age"] = i + 1;
    dr["Name"] = "Name" + i;
    dr["Sex"] = i % 2 == 0 ? "男" : "女";
    dr["IsMarry"] = i % 2 &gt; 0 ? true : false;
    dt.Rows.Add(dr);
}
Console.WriteLine(JsonConvert.SerializeObject(dt));
</code></pre>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/281443553617087.png" alt="" loading="lazy"></p>
<p>  利用上面字符串进行反序列化</p>
<pre><code class="language-c#">string json = JsonConvert.SerializeObject(dt);
dt = JsonConvert.DeserializeObject&lt;DataTable&gt;(json);
foreach (DataRow dr in dt.Rows)
{
    Console.WriteLine("{0}\\t{1}\\t{2}\\t{3}\\t", dr, dr, dr, dr);
}
</code></pre>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/281448403619018.png" alt="" loading="lazy"></p>
<p>  <code>Entity</code>序列化和<code>DataTable</code>一样,就不过多介绍了。</p>
<h2 id="三高级用法">三、高级用法</h2>
<ol>
<li>忽略某些属性</li>
<li>默认值的处理</li>
<li>空值的处理</li>
<li>支持非公共成员</li>
<li>日期处理</li>
<li>自定义序列化的字段名称</li>
<li>动态决定属性是否序列化</li>
<li>枚举值的自定义格式化问题</li>
<li>自定义类型转换</li>
<li>全局序列化设置</li>
</ol>
<h3 id="1忽略某些属性">1.忽略某些属性</h3>
<p>  我们在序列化的过程中,并不是所有属性都需要序列化的,如果实体中有些属性不需要序列化,可以使用该特性。首先介绍<code>Json.Net</code>序列化的模式:<code>OptOut</code>和<code>OptIn</code></p>
<table>
<thead>
<tr>
<th>模式</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>OptOut</td>
<td>默认值,类中所有公有成员会被序列化,如果不想被序列化,可以用特性<code>JsonIgnore</code></td>
</tr>
<tr>
<td>OptIn</td>
<td>默认情况下,所有的成员不会被序列化,类中的成员只有标有特性<code>JsonProperty</code>的才会被序列化,当类的成员很多,但客户端仅仅需要一部分数据时,很有用</td>
</tr>
</tbody>
</table>
<p>  <strong>仅需要姓名属性</strong></p>
<pre><code class="language-c#">
public class Person
{
        public int Age { get; set; }
       
        public string Name { get; set; }
        public string Sex { get; set; }
        public bool IsMarry { get; set; }
        public DateTime Birthday { get; set; }
}
</code></pre>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/281508379087828.png" alt="" loading="lazy"></p>
<p>  <strong>不需要是否结婚属性</strong></p>
<pre><code class="language-c#">
public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
    public string Sex { get; set; }
   
    public bool IsMarry { get; set; }
    public DateTime Birthday { get; set; }
}
</code></pre>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/281510410171151.png" alt="" loading="lazy"></p>
<p>  &nbsp;通过上面的例子可以看到,要实现不返回某些属性的需求很简单:</p>
<ol>
<li>在实体类上加上<code></code></li>
<li>在不需要返回的属性上加上<code></code>说明。</li>
</ol>
<h3 id="2默认值处理">2.默认值处理</h3>
<p> 序列化时想忽略默认值属性可以通过<code>JsonSerializerSettings.DefaultValueHandling</code>来确定,该值为枚举值</p>
<table>
<thead>
<tr>
<th>枚举值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>DefaultValueHandling.Ignore</td>
<td>序列化和反序列化时,忽略默认值</td>
</tr>
<tr>
<td>DefaultValueHandling.Include</td>
<td>序列化和反序列化时,包含默认值</td>
</tr>
</tbody>
</table>
<pre><code class="language-c#">
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));
</code></pre>
<p>  最终结果如下:</p>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/290700436652801.png" alt="" loading="lazy"></p>
<h3 id="3空值的处理">3.空值的处理</h3>
<p>序列化时需要忽略值为<code>NULL</code>的属性,可以通过<code>JsonSerializerSettings.NullValueHandling</code>来确定,另外通过<code>JsonSerializerSettings</code>设置属性是对序列化过程中所有属性生效的,想单独对某一个属性生效可以使用<code>JsonProperty</code>,下面将分别展示两个方式</p>
<p>  <strong>1)JsonSerializerSettings</strong></p>
<pre><code class="language-c#">Person p = new Person
{
    room = null,
    Age = 10,
    Name = "张三丰",
    Sex = "男",
    IsMarry = false,
    Birthday = new DateTime(1991, 1, 2)
};
JsonSerializerSettings jsetting = new JsonSerializerSettings();
jsetting.NullValueHandling = NullValueHandling.Ignore;
Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));
</code></pre>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/290707560092150.png" alt="" loading="lazy"></p>
<p>  <strong>2)JsonProperty</strong></p>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/290710397909914.png" alt="" loading="lazy"></p>
<p>  通过<code>JsonProperty</code>属性设置的方法,可以实现某一属性特别处理的需求,如默认值处理,空值处理,自定义属性名处理,格式化处理。上面空值处理实现</p>
<pre><code class="language-c#">
public Room room { get; set; }
</code></pre>
<h3 id="4支持非公共成员">4.支持非公共成员</h3>
<p>  序列化时默认都是处理公共成员,如果需要处理非公共成员,就要在该成员上加特性<code></code></p>
<pre><code class="language-c#">
private int Height { get; set; }
</code></pre>
<h3 id="5日期处理">5.日期处理</h3>
<p>  对于<code>Dateime</code>类型日期的格式化就比较麻烦了,系统自带的会格式化成<code>iso</code>日期标准<code>"Birthday":"1991-01-02T00:00:00</code>,但是实际使用过程中大多数使用的可能是<code>yyyy-MM-dd</code>或者<code>yyyy-MM-dd HH:mm:ss</code>两种格式的日期,解决办法是可以将<code>DateTime</code>类型改成<code>string</code>类型自己格式化好,然后再序列化。如果不想修改代码,可以采用下面方案实现。</p>
<p>  Json.Net提供了<code>IsoDateTimeConverter</code>日期转换这个类,可以通过<code>JsnConverter</code>实现相应的日期转换</p>
<pre><code class="language-c#">
public DateTime Birthday { get; set; }
</code></pre>
<p>  但是<code>IsoDateTimeConverter</code>日期格式不是我们想要的,我们可以继承该类实现自己的日期</p>
<pre><code class="language-c#">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);
    }
}
</code></pre>
<p>  自己实现了一个<code>yyyy-MM-dd</code>格式化转换类,可以看到只是初始化<code>IsoDateTimeConverter</code>时给的日期格式为<code>yyyy-MM-dd</code>即可,下面看下效果</p>
<pre><code class="language-c#">
public DateTime Birthday { get; set; }
</code></pre>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/290750116036578.png" alt="" loading="lazy"></p>
<p>  可以根据自己需求实现不同的转换类</p>
<h3 id="6自定义序列化的字段名称">6.自定义序列化的字段名称</h3>
<p>  实体中定义的属性名可能不是自己想要的名称,但是又不能更改实体定义,这个时候可以自定义序列化字段名称。</p>
<pre><code class="language-c#">
public string Name { get; set; }
</code></pre>
<h3 id="7动态决定属性是否序列化">7.动态决定属性是否序列化</h3>
<p>  根据某些场景,可能<code>A</code>场景输出<code>A</code>,<code>B</code>,<code>C</code>三个属性,<code>B</code>场景输出<code>E</code>,<code>F</code>属性。虽然实际中不一定存在这种需求,但是<code>json.net</code>依然可以支持该特性。</p>
<p>  继承默认的<code>DefaultContractResolver</code>类,传入需要输出的属性</p>
<p>重写修改了一下,大多数情况下应该是要排除的字段少于要保留的字段,&nbsp;&nbsp;为了方便书写这里修改了构造函数加入<code>retain</code>表示<code>props</code>是需要保留的字段还是要排除的字段</p>
<pre><code class="language-c#">public class LimitPropsContractResolver : DefaultContractResolver
{
    string[] props = null;
    bool retain;
    /// &lt;summary&gt;
    /// 构造函数
    /// &lt;/summary&gt;
    /// &lt;param name="props"&gt;传入的属性数组&lt;/param&gt;
    /// &lt;param name="retain"&gt;true:表示`props`是需要保留的字段`false`:表示`props`是要排除的字段&lt;/param&gt;
    public LimitPropsContractResolver(string[] props, bool retain = true)
    {
      //指定要序列化属性的清单
      this.props = props;
      this.retain = retain;
    }

    protected override IList&lt;JsonProperty&gt; CreateProperties(Type type, MemberSerialization memberSerialization)
    {
      IList&lt;JsonProperty&gt; list = base.CreateProperties(type, memberSerialization); //只保留清单有列出的属性
      return list.Where(p =&gt;
      {
            if (retain)
            {
                return props.Contains(p.PropertyName);
            }
            else
            {
                return !props.Contains(p.PropertyName);
            }
      }).ToList();
    }
}
</code></pre>
<pre><code class="language-c#">public int Age { get; set; }

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));
</code></pre>
<p>  使用自定义的解析类,只输出<code>Age</code>、<code>IsMarry</code>两个属性,看下最终结果。只输出了<code>Age</code>属性,为什么<code>IsMarry</code>属性没有输出呢,因为标注了<code></code></p>
<p>  <img src="https://images0.cnblogs.com/blog2015/336693/201506/292128345097430.png" alt="" loading="lazy"></p>
<p>  看到上面的结果想要实现<code>pc</code>端序列化一部分,手机端序列化另一部分就很简单了吧,我们改下代码实现一下</p>
<pre><code class="language-c#">string[] propNames = null;
if (p.Age &gt; 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));
</code></pre>
<h3 id="8枚举值的自定义格式化问题">8.枚举值的自定义格式化问题</h3>
<p>  默认情况下对于实体里面的枚举类型系统是格式化成改枚举对应的整型数值,那如果需要格式化成枚举对应的字符怎么处理呢?<code>Newtonsoft.Json</code>也帮我们想到了这点,下面看实例</p>
<pre><code class="language-c#">public enum NotifyType
{
    /// &lt;summary&gt;
    /// Emil发送
    /// &lt;/summary&gt;
    Mail = 0,
    /// &lt;summary&gt;
    /// 短信发送
    /// &lt;/summary&gt;
    SMS = 1
}
public class TestEnmu
{
    /// &lt;summary&gt;
    /// 消息发送类型
    /// &lt;/summary&gt;   
    public NotifyType Type { get; set; }
}
JsonConvert.SerializeObject(new TestEnmu());
</code></pre>
<p>  输出结果:<img src="https://images2015.cnblogs.com/blog/336693/201509/336693-20150910171743028-388882267.png" alt="" loading="lazy"></p>
<p>  现在改造一下,输出<code>"Type":"Mail"</code></p>
<pre><code class="language-c#">public class TestEnmu
{
    /// &lt;summary&gt;
    ///消息发送类型
    /// &lt;/summary&gt;
   
    public NotifyType Type { get; set; }
}
</code></pre>
<p>  其它的都不变,在<code>Type</code>属性上加上了<code>JsonConverter(typeof(StringEnumConverter))</code>表示将枚举值转换成对应的字符串,而<code>StringEnumConverter</code>是<code>Newtonsoft.Json</code>内置的转换类型,最终输出结果</p>
<p><img src="https://images2015.cnblogs.com/blog/336693/201509/336693-20150910172109044-1714459886.png" alt="" loading="lazy"></p>
<h3 id="9自定义类型转换">9.自定义类型转换</h3>
<p>  默认情况下对于实体里面的<code>Boolean</code>系统是格式化成<code>true</code>或者<code>false</code>,对于<code>true</code>转成"是" <code>false</code>转成"否"这种需求改怎么实现了?我们可以自定义类型转换实现该需求,下面看实例</p>
<pre><code class="language-c#">public class BoolConvert : JsonConverter
{
    private string[] arrBString { get; set; }

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

    /// &lt;summary&gt;
    /// 构造函数
    /// &lt;/summary&gt;
    /// &lt;param name="BooleanString"&gt;将`bool`值转换成的字符串值&lt;/param&gt;
    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, StringComparison.OrdinalIgnoreCase))
                {
                  return true;
                }
                else if (boolText.Equals(arrBString, 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));
    }

    /// &lt;summary&gt;
    /// 判断是否为`Bool`类型
    /// &lt;/summary&gt;
    /// &lt;param name="objectType"&gt;类型&lt;/param&gt;
    /// &lt;returns&gt;为`bool`类型则可以进行转换&lt;/returns&gt;
    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" &amp;&amp; t.GetGenericTypeDefinition() == typeof(Nullable&lt;&gt;));
    }

    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);
      }
      else
      {
            writer.WriteValue(arrBString);
      }
    }
}
</code></pre>
<p>  自定义了<code>BoolConvert</code>类型,继承自<code>JsonConverter</code>。构造函数参数<code>BooleanString</code>可以让我们自定义将<code>true false</code>转换成相应字符串。下面看实体里面怎么使用这个自定义转换类型</p>
<pre><code class="language-c#">public class Person
{
   
    public bool IsMarry { get; set; }
}
</code></pre>
<p>  <img src="https://images2015.cnblogs.com/blog/336693/201512/336693-20151214101004068-161668511.png" alt="" loading="lazy">‘</p>
<p>  相应的有什么个性化的转换需求,都可以使用自定义转换类型的方式实现。</p>
<h3 id="10全局序列化设置">10.全局序列化设置</h3>
<p>  文章开头提出了<code>Null</code>值字段怎么不返回的问题,相应的在高级用法也给出了相应的解决方案使用<code>jsetting.NullValueHandling = NullValueHandling.Ignore;</code>来设置不返回空值。这样有个麻烦的地方,每个不想返回空值的序列化都需设置一下。可以对序列化设置一些默认值方式么?下面将解答</p>
<pre><code class="language-c#">Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
JsonConvert.DefaultSettings = new Func&lt;JsonSerializerSettings&gt;(() =&gt; {//日期类型默认格式化处理
    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;
});
</code></pre>
<p>  这样设置以后,以后使用序列化的地方就不需要单独设置了,个人最喜欢设置的是空值处理这一块。</p>
<h2 id="四总结">四、总结</h2>
<p>  今天就到这里了,它涉及的内容太多了,我们只是接触了皮毛,官网地址:https://www.newtonsoft.com/json。</p><br><br>
来源:https://www.cnblogs.com/zhaoshujie/p/11077843.html
頁: [1]
查看完整版本: C# Newtonsoft.Json 高级用法