学无先后达者为师!
不忘初心,砥砺前行。

分享一些在 dotnet 中处理 JSON 的常规及非主流操作【第一篇】

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)•••
KeyValue
NameBad Boys
TypeMovie

复杂 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 就是转换好的结果,可以直接使用。

赞(1) 打赏
未经允许不得转载:码农很忙 » 分享一些在 dotnet 中处理 JSON 的常规及非主流操作【第一篇】

评论 抢沙发

给作者买杯咖啡

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫

登录

找回密码

注册