Json.NET 是 .NET 平台中非常流行的高性能 JSON 处理框架。仅通过一行代码即可实现 JSON 序列化和反序列化:
序列化 JSON
Product product = new Product(); product.Name = "Apple"; product.Expiry = new DateTime(2008, 12, 28); product.Sizes = new string[] { "Small" }; string json = JsonConvert.SerializeObject(product); // { // "Name": "Apple", // "Expiry": "2008-12-28T00:00:00", // "Sizes": [ // "Small" // ] // }
反序列化 JSON
string json = @"{ 'Name': 'Bad Boys', 'ReleaseDate': '1995-4-7T00:00:00', 'Genres': [ 'Action', 'Comedy' ] }"; Movie m = JsonConvert.DeserializeObject<Movie>(json); string name = m.Name; // Bad Boys
反序列化匿名类
如果你不想创建新类型,那也可以直接使用匿名类进行反序列化(序列化当然也是支持的):
string json = @"{ 'Name': 'Bad Boys', 'ReleaseDate': '1995-4-7T00:00:00', 'Genres': [ 'Action', 'Comedy' ] }"; var m = JsonConvert.DeserializeAnonymousType(json, new { Name = string.Empty, Genres = new List<String>() }); string name = m.Name; // Bad Boys
反序列化为动态类型
如果你对 JSON 的格式完全信任,也可以直接使用动态类型,仅需在泛型参数中传入 dynamic
:
string json = @"{ 'Name': 'Bad Boys', 'ReleaseDate': '1995-4-7T00:00:00', 'Genres': [ 'Action', 'Comedy' ] }"; var m = JsonConvert.DeserializeObject<dynamic>(json); string name = m.Name; // Bad Boys
LuYao.Text.Json
作为一个小工具,LuYao.Text.Json 可以通过 Nuget 直接安装。
Nuget 地址:https://www.nuget.org/packages/LuYao.Text.Json
智能 JSON 字符串抽取
在做数据清理时,经常会遇到从 HTML 中抽取 JSON 片段的需求:复杂的 HTML 页面中有大段大段的 JSON,而只有其中的很小一部分对我们是有用的。因为特征的存在,确定 JSON 的开始位置很容易,但结束位置却比较难。所以我封装了 JsonUtils.ExtractJson
方法用于解决这个问题。
string json = @"{ 'Name': 'Bad Boys', 'ReleaseDate': '1995-4-7T00:00:00', 'Genres': [ 'Action', 'Comedy' ] }"; var keyword = "Genres':"; var index = json.IndexOf(keyword); var forSearch = json.Substring(index+keyword.Length); var genresJson = JsonUtils.ExtractJson(forSearch); // ["Action","Comedy"]
按属性名抽取数据值
JsonUtils.ExtractValueByNames
方法可以用来根据属性名称从 JSON 中抽取数据值。该方法返回一个 NameValueCollection
,无视 JSON 层级,只要属性名匹配即可返回。
string json = @"{ 'Name': 'Bad Boys', 'ReleaseDate': '1995-4-7T00:00:00', 'Genres': [ 'Action', 'Comedy' ], 'Type': 'Movie' }"; var nv = JsonUtils.ExtractValueByNames(json,"Name","Type");
执行结果:
NameValueCollection (2 items)••• | |
Key | Value |
---|---|
Name | Bad Boys |
Type | Movie |
复杂 JSON 生成
在对接其他应用程序时,经常会遇到对接目标需要的数据结构很离谱的情况:
{ "Title": [ { "Text": "商品标题", "Language": "en-us" } ], "Description": [ { "Text": "商品描述", "Language": "en-us" } ] }
如果使用实体序列化来生成以上的 JSON,代码会非常冗长:每个属性都是数组,且 Language 属性多次出现。为此,我写了 JsToken
并放在了 LuYao.Text.Json.Tokens
命名空间下。
var jsProduct = new JsObject( new JsArray("Title", new JsObject( new JsString("Text", string.Empty) { Alias = "Title" }, new JsString("Language", string.Empty) ) ), new JsArray("Description", new JsObject( new JsString("Text", string.Empty) { Alias = "Description" }, new JsString("Language", string.Empty) ) ) ); var nv = new NameValueCollection(); nv["Title"] = "商品标题"; nv["Description"] = "商品描述"; nv["Language"] = "en-us"; var sb = new StringBuilder(); using (var sw = new StringWriter(sb)) using (var jw = new JsonTextWriter(sw)) { jsProduct.Output(nv,jw); } var json = sb.ToString(); //{"Title":[{"Text":"商品标题","Language":"en-us"}],"Description":[{"Text":"商品描述","Language":"en-us"}]}
终极大杀器: JavaScript 解释器
上文所述的方案,仅能解决输入复杂或者输出复杂的情况。如果输入和输出都比较复杂有一个可行的方案就是让大哥登场:程序中引入 JavaScript 。
要想在 .NET 平台使用 JavaScript,一个跨平台的解决方案是: Jint。
https://www.nuget.org/packages/Jint
注意,你要使用 3.x 版本,且该版本仍处于预览状态。
路遥工具箱中有一个名为《JSON 转换》的功能:
该功能支持输入一个 JSON 文件和一个 JavaScript 文件,并给出运行结果。
假设有数据文件 d.json 的内容如下:
{ "Name": "Bad Boys", "ReleaseDate": "1995-4-7T00:00:00", "Genres": [ "Action", "Comedy" ] }
同时又有脚本文件 t.js 的内容如下:
var ret = {}; ret.Name = model.Name; ret.Genres = model.Genres; for(var idx in model.Genres) { if(model.Genres[idx] == 'Action') { ret.HasAction = true; } } return ret;
则带入界面后的输出为:
{ "Name": "Bad Boys", "Genres": [ "Action", "Comedy" ], "HasAction": true }
工具箱会监测文件的变更,如果文件有改动会自动对结果进行重算。
TranslateableJsonModel
TranslateableJsonModel
因为本身较为简单且需要依赖更多的第三方库,所以没有直接集成在 LuYao.Text.Json
里,而是以源代码的形式粘贴在本文中:
public abstract class TranslateableJsonModel<T> where T : TranslateableJsonModel<T> { public static readonly string JavaScriptConverter; static TranslateableJsonModel() { var type = typeof(T); var xsltFileNames = new string[] { type.FullName+".js" }; foreach (var name in xsltFileNames) { using (var ms = type.Assembly.GetManifestResourceStream(name)) { if (ms == null) continue; using (var sr = new StreamReader(ms)) JavaScriptConverter = sr.ReadToEnd(); break; } } if (string.IsNullOrWhiteSpace(JavaScriptConverter)) throw new Exception($"没有找到与类型名相同的 js 文件,请确保文件目录与类型的命名空间一致且已经被设置为”嵌入的资源“。"); } public static T Transform(string json) { using (var e = new Engine()) { if (!string.IsNullOrWhiteSpace(json)) { JsonConvert.DeserializeObject(json); e.Evaluate($"var model = {json};"); } var result = BuildOutput(e.Evaluate(JavaScriptConverter)); return JsonConvert.DeserializeObject<T>(result); } } private static string BuildOutput(JsValue value) { if (value.IsObject()) { var sb = new StringBuilder(); using (var sw = new StringWriter(sb)) { var w = new JsonTextWriter(sw) { Formatting = Newtonsoft.Json.Formatting.Indented }; Write(value, w); return sb.ToString(); } } return value.ToString(); } private static void Write(JsValue value, Newtonsoft.Json.JsonWriter w) { switch (value) { case ArrayInstance array: w.WriteStartArray(); foreach (var item in array) Write(item, w); w.WriteEndArray(); break; case JsDate date: w.WriteValue(date.ToDateTime()); break; case JsNumber number: w.WriteRawValue(number.ToString()); break; case JsBigInt bigInt: w.WriteValue(bigInt.ToObject()); break; case JsBoolean boolean: w.WriteValue(boolean.ToObject()); break; case ObjectInstance instance: w.WriteStartObject(); foreach (var item in instance.GetOwnProperties()) { w.WritePropertyName(item.Key.ToString()); Write(item.Value.Value, w); ; } w.WriteEndObject(); break; case JsString str: w.WriteValue(str.ToString()); break; default: w.WriteRawValue(value.ToString()); break; } } }
需要引入以下命名空间:
using Jint; using Jint.Native; using Jint.Native.Array; using Jint.Native.Object; using Newtonsoft.Json; using System.Text;
TranslateableJsonModel 的使用方式
仍以上文中的 d.json 和 t.js 为例,可以新建一个名为 Movie 的类型,并从 TranslateableJsonModel
派生:
public class Movie : TranslateableJsonModel<Movie> { public string Name { get; set; } public List<string> Genres { get; set; } public bool HasAction { get; set; } }
接着,将 t.js 改名为 Movie.js
,放置在和 Movie.cs 相同的目录下,设置【生成操作】为【嵌入的资源】。
string json = @"{ 'Name': 'Bad Boys', 'ReleaseDate': '1995-4-7T00:00:00', 'Genres': [ 'Action', 'Comedy' ] }"; var m = Movie.Transform(json);
变量 m
就是转换好的结果,可以直接使用。