KoobooJson - 更小更快的C# JSON序列化工具(基于表达式树构建)

  在C#领域,有很多成熟的开源JSON框架,其中最著名且使用最多的是 Newtonsoft.Json ,然而因为版本迭代,其代码要兼容从net2.0到现在的最新的net框架,并且要支持.net平台下的其它语言,所以最新发布版本的Newtonsoft.Json其dll大小接近700k,另一方面,因为其复杂的迭代历史导致它的代码为了维护向下扩展性和向上兼容性而舍弃一些性能。

  如果你不太在乎体积和性能的话,那么 Newtonsoft.Json 无疑是一款很好的选择。但是如果你在意性能的话,在github上仍然有一些出名的以速度为称的c# JSON框架,其中最为人知的应该是 JIL , JIL有着出色的性能是因为它采用了大量的加速技术,但这也带来了一些局限性,它不够灵活,对object类型的解析必须得调用它的另一个API,并且因为出于性能考虑其采用的是Emit技术,不易维护,在我的测试中有很多类型它不支持。但是JIL的地位是显而易见的,因为它的出现,github上有着很多相仿思路的以速度为称的JSON框架,几乎每个都称自己是最快的,但实际上很少有超越JIL的,并且它们中的大部分没有一个良好的文档,这导致我在做性能测试时,我想改个配置都得对源码全局搜索花费一定时间。

  在说回程序集大小,JIL的最新发布版本是500k,并且其依赖一个库,加起来是800k大小。

  那么,我讲这些,大家应该知道我想要表达什么!

  是的,考虑到前面种种,这些都不是在某种场景最理想化的那种JSON库,所以我写了一款以体积更小,速度更快,类型覆盖更广的开源C# JSON框架,它叫:KoobooJson

在我正式介绍KoobooJson之前,我要介绍一下什么是Kooboo

Kooboo是我们老板用C#编写的一个开源的非常神奇的网站开发工具,它是一个类CMS生成器,但其从数据库,前端引擎,到各种网络协议服务器都是用c#自主创造的,几乎很少使用到第三方库,它编译后的发布版本仅有几M,而正是因为仅仅只有几M,为了Json框架不要太影响主程序的大小,这才有了KoobooJson此次的诞生!

Kooboo是开源的:https://github.com/Kooboo/Kooboo

KoobooJson自然也是开源的:https://github.com/Kooboo/Json

在NuGet包中可以直接搜索 KoobooJson 下载使用即可

什么是KoobooJson?

  KoobooJson是一款C#的JSON工具,其主要通过表达式技术构建,最低支持.NET4.5(可以支持.NET4.0,但考虑到一些因素,最终没有支持,有需要支持的可以自行源码分支更改。另外,几乎每个以性能号称的JSON框架都最低支持.NET4.5),最低支持.NET Core 2.0,体积小巧,性能出色,类型覆盖广是KoobooJson的优点!

KoobooJson的优点

1. 小巧

目前KoobooJson只有130k, 并且没有任何额外依赖项, KoobooJson当前支持框架版本.NET4.5 .NET Core2+ .NET Standard 2

2. 快速

KoobooJson 遵循JSON RFC8259规范, 是一款适用于C#的快速的Json文本序列化器

它基于表达式树构建, 在运行时会动态的为每个类型生成高效的解析代码, 这过程包括: 利用静态泛型模板进行缓存, 避免字典查询开销, 避免装箱拆箱消耗, 缓冲池复用, 加速字节复制...

KoobooJson生成代码的方式并没有采用Emit, 而是采用ExpressionTree. ExpressionTree相比Emit而言, 它不能像Emit直接写出最优的IL代码, 它依赖于下层的编译器, 在某些时候它会多生成一些不必要的IL代码路径, 故而性能上有所逊色. 但相较于几乎没有类型检查的Emit而言, ExpressionTree不会出现各种莫名其妙的错误, 它更加安全, 也更加容易扩展维护.

虽然ExpressionTree与Emit相比在性能方面可能会有所差异, 但是KoobooJson的表现却相当亮眼!

上图是使用BenchmarkDotNet在Net Core2.1上做的Json序列化和反序列化的性能测试,随机生成大量的测试数据,迭代100次后产生的结果,基准报告在这里

BenchmarkDotNet=v0.11.4, OS=Windows 10.0.17763.316 (1809/October2018Update/Redstone5) Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=2.1.505 [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT Job-XEQPPS : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT

IterationCount=100 LaunchCount=1 WarmupCount=1

3. 覆盖类型广

在类型定义上, KoobooJson并没有单独实现每个集合或键值对类型, 而是对这些FCL类型进行划分成不同的模板

a. KoobooJson将序列化分为5种类型:

  • 原始类型
    它包括 Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single.

  • 所有拥有键值对行为的类型
    任何能够实现IDictionary<>或能够实现IDictionary且能够通过构造函数注入键值对的类型, 都将以键值对方式进行解析

  • 所有拥有集合行为的类型

    任何能够实现IEnumable并且满足IColloction的Add行为或拥有自己独特的集合行为且能够通过构造函数注入集合的类型, 都将以集合方式进行解析

  • 特殊类型

    如Nullable<>, Lazy<>, Guid, Datatable, DateTime, Type, Task, Thread, Timespan...等等这些特定的类型实现

  • 常规Model的键值对类型

    在KoobooJson中, 如果当类型不满足上述4种时, 将会以键值对的形式来对其解析, KoobooJson会对Model中公开的所有元素进行序列化, 在这个环节, 几乎配置器中所有的配置都是有关Model的. 诸如别名, 忽略特性, 指定构造函数, 忽略堆栈循环引用, 首字母大小写, 格式化器... 值得一提的是, 在对接口类型进行反序列化时, KoobooJson默认会自动创建并返回一个实现于该接口的对象.

b. 在对类型的解析上, 其中浮点型,日期时间类型, GUID的解析是参照了JIL的代码, 在此表示感谢.

作为一款活跃的Json库, KoobooJson会不断支持更多的类型, 这其中, 因为对FCL中的键值对和集合的行为进行归纳, 所以对于这两种类型, KoobooJson并不像其它框架一样去特定的为每种类型单独实现, 实际上, 第2和3所定义的规则可以容纳FCL中的大多数键值对或集合类型.

目前KoobooJson所覆盖的类型包括 : Hashtable, SortedList, ArrayList, IDictionary, Dictionary<,>, IList,List<>, IEnumerable<>, IEnumerable, ICollection, ICollection<>, Stack<>, Queue<>, ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, SortedDictionary<,>, ConcurrentDictionary<,>, SortedList<,>, IReadOnlyDictionary<,>, ReadOnlyDictionary<,>, ObservableCollection<>, HashSet<>, SortedSet<>, LinkedList<>, ReadOnlyCollection<>, ArraySegment<>, Stack, Queue, IReadOnlyList<>, IReadOnlyCollection<>, ReadOnlyCollection<>, ISet<>, BitArray, URI, NameValueCollection, StringDictionary, ExpandoObject, StringBuilder, Nullable<>, Lazy<>, Guid, Datatable, DateTime, Type, Task, Thread, Timespan, Enum, Exception, Array[], Array[,,,,,]...

KoobooJson的实现

序列化

class UserModel
{
public object Obj;
public string Name;
public int Age;
}
string json = JsonSerializer.ToJson(new UserModel());

在对类型第一次序列化时, KoobooJson会为这个类型生成大致是这样的解析代码.

void WriteUserModel(UserModel model,JsonSerializerHandler handler)
{
...配置选项处理...格式化器处理...堆栈无限引用处理...
handler.sb.Write("Obj:")
WriteObject(model.Obj);//在序列化时将为Object类型做二次真实类型查找

handler.sb.Write("Name:")
WriteString(model.Name);

handler.sb.Write("Age:")
WriteInt(model.Age); }

如果是List<UserModel>的话, 那么将生成这样的代码

handler.sb.Write("[")
foreach(var user in users)
{
WriteUserModel(user);
WriteComma()
}
handler.sb.Write("]")

在当前版本中, KoobooJson序列化使用的容器为StringBuilder, 与直接ref char[]相比, 多了一些额外的调用. 将考虑在下个版本中构建一个轻便的char容器, 并会区分对象大小, 考虑栈数组和通过预扫描大小来减少对内存的开销,这将显著提升序列化速度.

反序列化

在对类型进行第一次反序列化时, KoobooJson会为这个类型生成大致是这样的解析代码.

UserModel model = JsonSerializer.ToObject<UserModel>("{\"Obj\":3,\"Name\":\"Tom\",\"Age\":18}");
void ReadUserModel(string json,JsonDeserializeHandler handler)
{
...Null处理...
ReadObjLeft()
空元素处理...构造函数处理...配置项处理...格式化器处理...
while(i-->){
switch(gechar())
{
case 'O':
switch(getchar())
case 'b':
switch(getchar())
case 'j':
ReadQuote();
ReadObject();
if(getchar()==',')
i++;
}
}
ReadObjRight()
}

KoobooJson生成反序列化代码, KoobooJson会假设json格式完全正确, 没有预先读取Json结构部分, 而是直接使用代码来描述结构, 所以KoobooJson少了一次对json结构的扫描, 执行过程中如果json结构发生错误, 会直接抛出异常.

而对于key的匹配, KoobooJson生成的是逐个char的自动机匹配代码, 目前KoobooJson是以字典树为算法, 逐个char进行类型比较, 与一次比较多个char相比, 这种方式显然没有达到最小的查询路径, 不过在jit优化下, 两种方式实现经测试效率几乎一样.

在反序列化读取字符时, 因为是对类型动态生成编码, 提前知道每个类型中的元素的字节长度和其类型的值长度, 所以KoobooJson出于更高的性能对反序列化采取了指针操作, 并加速字节读取.

case :
if (*(int*)Pointer != *(int*)o) return false;
if (*(Pointer + ) != *(o + )) return false;
goto True;
case :
if (*(long*)Pointer != *(long*)o) return false;
goto True;
case :
if (*(long*)Pointer != *(long*)o) return false;
if (*(Pointer + ) != *(o + )) return false;

因为是指针操作, KoobooJson在反序列化环节几乎不需要去维护一个char池来存放下一个需要读取的json结构片段.

功能介绍

KoobooJson当前支持6个API调用

string Kooboo.Json.JsonSerializer.ToJson<T>(T value, JsonSerializerOption option=null)

T Kooboo.Json.JsonSerializer.ToObject<T>(string json, JsonDeserializeOption option=null)

object Kooboo.Json.JsonSerializer.ToObject(string json, Type type, JsonDeserializeOption option=null) void Kooboo.Json.JsonSerializer.ToJson<T>(T value, StreamWriter streamWriter, JsonSerializerOption option = null) T Kooboo.Json.JsonSerializer.ToObject<T>(StreamReader streamReader, JsonDeserializeOption option = null) object Kooboo.Json.JsonSerializer.ToObject(StreamReader streamReader, Type type, JsonDeserializeOption option = null)

忽略注释

在json字符串的读取中KoobooJson会自动忽略注释

string json = @"
/*注释*/
{//注释
/*注释*/""Name"" /*注释*/: /*注释*/""CMS"" /*注释*/,//注释
/*注释*/
""Children"":[//注释
1/*注释*/,
2/*注释*/
]//注释
}//注释
/*此处*/
";
var obj = JsonSerializer.ToObject(json);
obj=>Name:CMS
obj=>Children:Array()

忽略互引用所导致的堆栈循环

class A
{
public B b;
}
class B
{
public A a;
}
A.b=B;
B.a=A;

A指向B, B指向A, 在序列化时这种情况会发生无限循环.可通过KoobooJson的序列化配置项中的属性来设定这种情况下所对应的结果

JsonSerializerOption option = new JsonSerializerOption
{
ReferenceLoopHandling = JsonReferenceHandlingEnum.Null
};
string json = JsonSerializer.ToJson(a, option);
json => {\"b\":{\"a\":null}}
------
ReferenceLoopHandling = JsonReferenceHandlingEnum.Empty
json => {\"b\":{\"a\":{}}}
-----
ReferenceLoopHandling = JsonReferenceHandlingEnum.Remove
json => {\"b\":{}}

忽略Null值

class A
{
public string a;
}
A.a=null;
JsonSerializerOption option = new JsonSerializerOption { IsIgnoreValueNull = true };
var json = JsonSerializer.ToJson(A, option);
json => {}

排序特性

class A
{
[JsonOrder()]
public int a;
[JsonOrder()]
public int b;
[JsonOrder()]
public int c;
}

可通过[JsonOrder(int orderNum)]来排序序列化的json元素位置. 如果是正常没有通过[JsonOrder]排序元素,那么解析出来的Json则是默认顺序:{"a":0,"b":0,"c":0} 上面样例通过[JsonOrder]排序后是这样的:{"c":0,"b":0,"a":0}

Dictionary的Key格式

在Json规范中,键值对的键必须是字符串类型,在KoobooJson中,对Key的类型允许所有基元类型(Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single)和String,以及Datetime,GUID,Enum,共18种支持的类型

JObject和JArray

反序列化时,对Object的类型解析,最终将会产生5种结果: Bool,数值(long,ulong,double),String,JArray,JObject 其中,JArray代表着数组,它拥有List<object>的所有特性. JObject代表着键值对,它拥有Dictionary<string,object>的所有特性.

忽略默认值元素

[IgnoreDefaultValue]
class User
{
public int? Num { get; set; } public int Age { get; set; } public string Name { get; set; } }
var json = JsonSerializer.ToJson(new User());
json=> {}

如果你想在序列化时,忽略默认值的元素,那么可以对这个类型标记[IgnoreDefaultValue]特性,也可以标记在属性或者字段上

忽略序列化元素

class A
{
[IgnoreKey]
public int a;
public int b;
}

可通过[IgnoreKey]特性来标记序列化和反序列化要忽略的元素 json => {"b":0} 当然, 也可以通过配置来动态选择忽略对象

JsonSerializerOption option = new JsonSerializerOption { IgnoreKeys = new List<string>(){"b"} };
var json = JsonSerializer.ToJson(A, option);
json => {}

序列化时仅包含该元素

class A
{
[JsonOnlyInclude]
public int a;
public int b;
public int c;
}
json => {\"a\":0}

如果一个model里包含几十个元素, 而你仅想序列化其中一个, 那么就没必要对每一个元素进行[IgnoreKey]标记,只需要对想要序列化的元素标记[JsonOnlyInclude]即可

时间格式

JsonSerializerOption option = new JsonSerializerOption { DatetimeFormat=DatetimeFormatEnum.ISO8601 };
json => --02T03::05Z JsonSerializerOption option = new JsonSerializerOption { DatetimeFormat=DatetimeFormatEnum.RFC1123 };
json => Thu, Apr :: GMT JsonSerializerOption option = new JsonSerializerOption { DatetimeFormat=DatetimeFormatEnum.Microsoft };
json => \/Date()\/

首字母大小写

class A
{
public int name;
}
JsonSerializerOption option = new JsonSerializerOption { JsonCharacterRead=JsonCharacterReadStateEnum.InitialUpper };
json => {\"Name\":0}

在对model序列化时可以指定key的首字母大小写,反序列化时也可以设置对字符串不区分大小写.首字母大小写属于内嵌支持, 在解析时并不会影响性能

别名特性

 class A
{
[Alias("R01_Name")]
public int name;
}
json => {\"R01_Name\":0}

当元素被标记[Alias]后,KoobooJson无论序列化还是反序列化都会按照Alias来进行解析

反序列化时指定构造函数

class A
{
public A(){}
[JsonDeserializeCtor(,"ss")]
public A(int a,string b){}
}

在反序列化的时候, 我们不得不调用构造函数来以此创建对象. 在常规情况下, KoobooJson会通过优先级自动搜索最合适的构造函数,其优先级顺序为: public noArgs => private noArgs => public Args => private Args, 这其中, 会对有参构造函数进行默认值构造.

然而你也可以显式通过[JsonDeserializeCtor(params object[] args)]特性来指定反序列化时的构造函数, 这样 当KoobooJson创建A实例的时候就不是通过new A(); 而是new A(3,"ss");

值格式化特性

 class A
{
[Base64ValueFormat]
public byte[] a;
}

当你需要来覆写由KoobooJson进行元素解析的行为时, 我们可以继承一个 ValueFormatAttribute 来覆写行为.

 class Base64ValueFormatAttribute:ValueFormatAttribute
{
public override string WriteValueFormat(object value,Type type, JsonSerializerHandler handler, out bool isValueFormat)
{
isValueFormat=true;
if(value==null)
return "null";
else
return ConvertToBase64((byte[])value);
} public override object ReadValueFormat(string value,Type type, JsonDeserializeHandler handler, out bool isValueFormat)
{
isValueFormat=true;
if(value=="null")
return null;
else
return Base64Convert(value);
}
}

值格式化特性也可以标记在结构体或类上, 而另一点是对于值格式化器, 也可以以全局的方式来进行配置: 以序列化为例, 可通过 JsonSerializerOption中的GlobalValueFormat委托来进行配置

JsonSerializerOption.GlobalValueFormat=(value,type,handler,isValueFormat)=>
{
if(type==typeof(byte[]))
{
isValueFormat=true;
if(value==null)
return "null";
else
return ConvertToBase64((byte[])value);
}
else
{
isValueFormat=false;
return null;
}
}

值得注意的是,对于byte[]类型的base64解析行为, KoobooJson已经内嵌在配置项中, 只要设置JsonSerializerOption.IsByteArrayFormatBase64=true即可

全局Key格式化

对于Model中的Key处理, KoobooJson支持全局的Key格式化器.

 class R01_User
{
public string R01_Name;
public int R01_Age;
}

如果我们想把R01这个前缀给去掉, 只需要注册全局Key格式化器的委托即可

JsonSerializerOption.GlobalKeyFormat=(key,parentType,handler)=>
{
if(parentType==typeof(R01_User))
{
return key.Substring();
}
return key;
}

这样,出来的json是这样的:{"Name":"","Age":""}

同样, 对于反序列化,我们也同样应该注册:

JsonDeserializeOption.GlobalKeyFormat=(key,parentType)=>
{
if(parentType==typeof(R01_User))
{
return "R01_"+key;
}
return key;
}

其它

我喜欢和我一样的人交朋友,不被环境影响,自己是自己的老师,欢迎加群 .Net web交流群, QQ群:166843154 欲望与挣扎

作者:小曾
出处:https://www.cnblogs.com/1996V/p/10607916.html 欢迎转载,但请保留以上完整文章,在显要地方显示署名以及原文链接。
.Net交流群, QQ群:166843154 欲望与挣扎

开源 , KoobooJson一款高性能且轻量的JSON框架的更多相关文章

  1. Snack3 3.2 发布,轻量的Json+Jsonpath框架

    Snack3 是一个轻量的 JSON + Jsonpath 框架. 借鉴了 Javascript 所有变量由 var 申明,及 Xml dom 一切都是 Node 的设计.其下一切数据都以ONode表 ...

  2. Nancy总结(一)Nancy一个轻量的MVC框架

    Nancy是一个基于.net 和Mono 构建的HTTP服务框架,是一个非常轻量级的web框架. 设计用于处理 DELETE, GET, HEAD, OPTIONS, POST, PUT 和 PATC ...

  3. 轻量型ORM框架Dapper的使用

    在真实的项目开发中,可能有些人比较喜欢写SQL语句,但是对于EF这种ORM框架比较排斥,那么轻量型的Dapper就是一个不错的选择,即让你写sql语句了,有进行了关系对象映射.其实对于EF吧,我说下我 ...

  4. 轻量的web框架Bottle

    简洁的web框架Bottle 简介 Bottle是一个非常简洁,轻量web框架,与django形成鲜明的对比,它只由一个单文件组成,文件总共只有3700多行代码,依赖只有python标准库.但是麻雀虽 ...

  5. 九款免费轻量的 AutoCAD 的开源替代品推荐

    随着各行各业的发展,CAD已经广泛应用于工业.服装.建筑以及电子产业等设计领域.AutoCAD 是一种流行的商业CAD软件,虽然很强大,但并不是免费的.因此本文推荐了几种免费重量轻的CAD工具/软件, ...

  6. quilljs 一款简单轻量的富文本编辑器(适合移动端)

    quilljs入门使用教程: quill.js是一款强大的现代富文本编辑器插件.该富文本编辑器插件支持所有的现代浏览器.平板电脑和手机.它提供了文本编辑器的所有功能,并为开发者提供大量的配置参数和方法 ...

  7. laytpl : 一款非常轻量的JavaScript模板引擎

    //假设你得到了这么一段数据 var data = { title: '前端圈', intro: '一群码js的骚年,幻想改变世界,却被世界改变.', list: [{name: '贤心', city ...

  8. 【分享】一款特别轻量的gif生成工具

    github链接:https://github.com/NickeManarin/ScreenToGif/releases/tag/2.19.3 超级好用!支持多种方式(摄像头也可√)录制gif

  9. 之前项目使用的轻量的goweb框架

    技术栈 go 主开发语言 基于 gorilla 项目 javascript(nodejs) 部分小工具,josn对象转换,自动编译 C#,codesmith通用代码生成,生成最基本的crud和翻页. ...

随机推荐

  1. for in 使用

    // JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. 易于人阅读和编写. var json = { "id": 1, "n ...

  2. 自己整理的所有java知识点(不断迭代中)

    1. 自己整理的所有java知识点(不断迭代中) 画图工具注册 https://www.processon.com/i/599d35fae4b00d97d7f9bb17 1.1. Java整体知识架构 ...

  3. ASP.NET Core Middleware 抽丝剥茧

    一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件. 每个组件是pipeline 中的一环. 自行决定是否将请求传递给下一 ...

  4. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  5. 《HelloGitHub》第 31 期

    公告 网站新增了 Web 服务器排行榜.数据库排行榜 <HelloGitHub>第 31 期 兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣 ...

  6. 微信小程序开发01-小程序的执行流程是怎么样的?

    前言 我们这边最近一直在做基础服务,这一切都是为了完善技术体系,这里对于前端来说便是我们需要做一个Hybrid体系,如果做App,React Native也是不错的选择,但是一定要有完善的分层: ① ...

  7. Asp.Net进程外Session(状态服务器Session、数据库Session)

    介绍 我们知道,当浏览器关闭,或者网站重启的时候,会话就结束了.即Seesion就丢失了.(当Web.config配置文件改动,哪怕什么内容都不加,仅仅往配置文件中加一个空格都是改we.config变 ...

  8. 利用tornado实现表格文件预览

    项目介绍   本文将介绍笔者的一个项目,主要是利用tornado实现表格文件的预览,能够浏览的表格文件支持CSV以及Excel文件.预览的界面如下:   下面我们将看到这个功能是如何通过tornado ...

  9. 配置Nginx部署静态资源和自动跳转到https

    一.首先在阿里云后台添加域名解析: 二.两个网站的静态资源在以下目录: /www/temp/blog/public 三.在服务器端配置nginx: cd /etc/nginx/conf.d 添加两个文 ...

  10. mysql 盲注二分法python脚本

    import urllib import urllib2 def doinject(payload): url = 'xxxxxxxxxxxxxxxxxxxxx' values = {'injecti ...