创作背景
项目地址: https://debugst.github.io/STJson
在开发过程中难免会遇到需要处理json的时候,但是.Net中自带的库似乎有点一言难尽啊。最后虽然找到了Newstonsoft.Json感觉还不错,但是还是觉得有些不如意的地方,它的功能虽然强大但是一些操作也过于复杂让我并不是很喜欢,而傲娇的我又喜欢自己动手。于是就开发了STLib.Json,也尽可能的让他保持最简单的使用方式。
目前内置已处理的数据类型:
| .Net | STJsonValueType | .Net | STJsonValueType | 
|---|---|---|---|
| byte | Long | sbyte | Long | 
| short | Long | ushort | Long | 
| int | Long | uint | Long | 
| long | Long | ulong | Long | 
| float | Double | double | Double | 
| decimal | Double | bool | Boolean | 
| char | String | string | String | 
| DateTime | String | enum | Long or String | 
| Point | Array | PointF | Array | 
| Size | Array | SizeF | Array | 
| Rectangle | Array | RectangleF | Array | 
| Color | Array | DataTable | Object | 
| Array | Array | ICollection | Array | 
| IDectionary | Object | object | Object | 
STLib.Json与其他json解析库有点不同,并没有类似JObject和JArray之类的对象,仅仅只有一个STJson。我不是很理解为什么要搞那么多对象,为什么不能用一个对象解决呢?无论是对象还是数组它们不都是json数据格式吗?可能开发者需要更具对象是否是数组什么的单独做一些特别的处理,但是我提供了STJson.ValueType来判断当前json是什么类型。
var json_1 = new STJson();
Console.WriteLine("[json_1] - " + json_1.IsNullObject + " - " + json_1.ValueType);
var json_2 = STJson.New();
json_2.SetItem("key", "value");
Console.WriteLine("[json_2] - " + json_2.IsNullObject + " - " + json_2.ValueType);
var json_3 = new STJson();
json_3.Append(1, 2, 3);
Console.WriteLine("[json_3] - " + json_3.IsNullObject + " - " + json_3.ValueType);
var json_4 = new STJson();
json_4.SetValue(DateTime.Now);
Console.WriteLine("[json_4] - " + json_4.IsNullObject + " - " + json_4.ValueType);
var json_5 = STJson.CreateArray();          // made by static function
Console.WriteLine("[json_5] - " + json_5.IsNullObject + " - " + json_5.ValueType);
var json_6 = STJson.CreateObject();         // made by static function
Console.WriteLine("[json_6] - " + json_6.IsNullObject + " - " + json_6.ValueType);
var json_7 = STJson.FromObject(12);         // made by static function
Console.WriteLine("[json_3] - " + json_7.IsNullObject + " - " + json_7.ValueType);
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[json_1] - True - Undefined
[json_2] - False - Object
[json_3] - False - Array
[json_4] - False - Datetime
[json_5] - False - Array
[json_6] - False - Object
[json_7] - False - Long
STJson
正如上面所看到的STJson是一个中间数据类型,它既不是string也不是object,而是介于它们之间的数据类型,它可以非常灵活的被使用。
var st_json = new STJson()
    .SetItem("number", 0)               // SetItem 返回自身
    .SetItem("boolean", true)
    .SetItem("string", "this is string")
    .SetItem("datetime", DateTime.Now)
    .SetItem("array_1", STJson.CreateArray(123, true, "string"))
    .SetItem("array_2", STJson.FromObject(new object[] { 123, true, "string" }))
    .SetItem("object", new { key = "this is a object" })
    .SetItem("null", obj: null);
st_json.SetKey("key").SetValue("this is a test");
Console.WriteLine(st_json.ToString(4)); // 4 -> 用4个空格格式化
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "number": 0,
    "boolean": true,
    "string": "this is string",
    "datetime": "2023-04-22T21:12:30.6109410+08:00",
    "array_1": [
        123, true, "string"
    ],
    "array_2": [
        123, true, "string"
    ],
    "object": {
        "key": "this is a object"
    },
    "null": null,
    "key": "this is a test"
}
刚才在上面提到STJson既可以是object也可以是array,当时当执行var st_json = new STJson()时,st_json为空元素,即st_json.IsNullObject = true。因为此时无法确定st_json是对象还是数组或者是值。
当ValuteType = Array时,无法调用SetItem(..),当ValueType = Object时,无法调用Append(..)。但是通过SetValue(..)可强制改变ValueType。
序列化
通过上面的例子或许你已经知道怎么将一个对象转换为string,通过STJson.FromObject(object).ToString(..)即可,但是有没有可能,其实不用这么麻烦的?比如:STJson.Serialize(..)就可以了???
事实上STJson.Serialize(..)的效率会更好,因为它是直接将对象转换为字符串,而不是转换成STJson再转换成字符串。
Console.WriteLine(STJson.Serialize(new { key = "this is test" }));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{"key":"this is test"}
当然你可以有个更友好的输出格式:
Console.WriteLine(STJson.Serialize(new { key = "this is test" }, 4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "key": "this is test"
}
事实上格式化输出是通过调用静态函数STJson.Format(..)完成的。如果你觉得不喜欢作者内置的格式化风格,完全可以自己写一个格式化的函数。或者去修改源码文件STJson.Statics.cs:Format(string,int)。
反序列化
事实上代码并不会直接将string转换为object。因为在那之前必须先对字符串进行解析,确保它是一个正确格式的json。但是做完这个过程的时候已经得到一个STJson对象了。最后将STJson再转换为object。
所以你会在源代码STLib.Json.Converter中看到如下文件:
ObjectToSTJson.cs ObjectToString.cs STJsonToObject.cs StringToSTJson.cs
里面并没有StringToObject.cs文件,而STJson.Deserialize(..)的源码如下:
public static T Deserialize<T>(string strJson, ..) {
    var json = StringToSTJson.Get(strJson, ..);
    return STJsonToObject.Get<T>(json, ..);
}
如何将字符串转换为对象,相信作者不用说明读者也应该知道如何处理,但是这里值得说明的是,STJson可以附加到对象中,实现局部更新。
public class TestClass {
    public int X;
    public int Y;
}
TestClass tc = new TestClass() {
    X = 10,
    Y = 20
};
STJson json_test = new STJson().SetItem("Y", 100);
STJson.Deserialize(json_test, tc);
Console.WriteLine(STJson.Serialize(tc));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
 {"X":10,"Y":100}
关于其他的一些用法如STJsonConverter,自定义转换器等,请自行查看教程文档。这里仅仅是对项目功能做一些基本介绍。
测试数据
[{
    "name": "Tom", "age": 16, "gender": 0,
    "hobby": [
        "cooking", "sing"
    ]
},{
    "name": "Tony", "age": 16, "gender": 0,
    "hobby": [
        "game", "dance"
    ]
},{
    "name": "Andy", "age": 20, "gender": 1,
    "hobby": [
        "draw", "sing"
    ]
},{
    "name": "Kun", "age": 26, "gender": 1,
    "hobby": [
        "sing", "dance", "rap", "basketball"
    ]
}]
var json_src = STJson.Deserialize(System.IO.File.ReadAllText("./test.json"));
此数据将在下面的案例中使用到。之后出现的json_src则为以上数据。
STJsonPath
在源码STJsonExtension.cs中对STJson的功能进行了扩展,里面集成一些STJsonPath的功能。所以在STJson的原始代码中并没有对STJsonPath的依赖,STJson可独立使用。但STJsonPath作为STJson的辅助类,需依赖STJson。
目前STJsonPath支持下列选择器:
| token | note | 
|---|---|
| $ | 根节点选择器,可视作代表根节点对象。 | 
| @ | 当前元素选择器,在遍历过程中指代当前被遍历的元素。 | 
| * | 通配符,表示可以代表任何一个节点。 | 
| .<name> | 子节点选择器,指定子节点的 key。 | 
| … | 深度选择器,表示可以是任意路径。 | 
| [‘<name>’(,‘<name>’)] | 列表选择器,指定子节点的 key集合。 | 
| [<number>(,<number>)] | 列表选择器,指定子节点的 index集合。 | 
| [Start:End:Step] | 切片选择器,用于指定索引区间。 | 
| [(<expression>)] | 表达式选择器,用于输入一个运算表达式,并将结果作为索引继续向下选择。 | 
| [?(<expression>)] | 表达式选择器,用于输入一个运算表达式,并将结果转换为布尔值,决定是否继续选择。 | 
通过以下方式可以构建一个STJsonPath:
// var jp = new STJsonPath("$[0]name");
// var jp = new STJsonPath("$[0].name");
var jp = new STJsonPath("[0]'name'"); // 以上方式均可以使用 $不是必须的
Console.WriteLine(jp.Select(json_src));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
["Tom"]
当然在STJson中的扩展函数中已经集成STJsonPath,可以通过下面的方式直接使用:
// var jp = new STJsonPath("[0].name");
// Console.WriteLine(json_src.Select(jp));
Console.WriteLine(json_src.Select("[0].name")); // 内部动态构建 STJsonPath
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
["Tom"]
STJsonPath以数组的方式返回数据,其返回值是STJson而不是List<STJson>,STJson也可以是数组对象。
$开头对于STJsonPath来说并不是必须的,且内部会移除掉开头的$或者@,$ @仅在表达式中作为对象的变量使用。
可能与其他JsonPath不同STJsonPath可以支持很复杂的表达式,为此特地编写了一个语法分析器STJsonPathParser.cs有兴趣的小伙伴可以自行查看代码。并且STJsonPath中内置了十几个内置函数可以调用。
运算符:
&& ||
 < <= > >= == != re
 & | << >> ^ ~
 + -
 * / %
 in nin anyof
 !
优先级从上往下依次递增。
| operator | note | e.g | 
|---|---|---|
| re | 正则表达式 | [?(@.name re ‘un’)] | 
| in | 左边的值或数组包含在右边的数组中 | [?(@.age in [16,20])] | 
| nin | 左边的值或数组不包含在右边的数组中 | [?(@.hobby nin [‘sing’,‘draw’])] | 
| anyof | 左边的值或数组和右边的数组存在交集 | [?(@.hobby anyof [‘sing’,‘draw’])] | 
内置函数:
| return | signature | note | 
|---|---|---|
| string | typeof(+n) | 获取数据类型。 | 
| string | str(+n) | 转换为字符串。 | 
| string | upper(+n) | 转换为大写。 | 
| string | lower(+n) | 转换为小写。 | 
| long | len(+n) | 获取字符串或者数组长度。 | 
| long | long(+n) | 转换为整数。 | 
| double | double(+n) | 转换为浮点数。 | 
| long or double | abs(+n) | 获取绝对值。 | 
| long | round(+n) | 四舍五入。 | 
| long | ceil(+n) | 向上取整。 | 
| long or double | max(+1) | 求最大值。 | 
| long or double | min(+1) | 求最小值。 | 
| long or double | avg(+1) | 求平均值。 | 
| long or double | sum(+1) | 求总和。 | 
| string | trim(+n) | 裁切字符串两端的指定字符。 | 
| string | trims(+n) | 裁切字符串开始的指定字符。 | 
| string | trime(+n) | 裁切字符串末尾的指定字符。 | 
| string_array | split(+1) | 拆分字符串。 | 
| long or string | time(+n) | 获取或格式化时间戳。 | 
STJsonPath支持自定义函数扩张,详细请自行查看教程文档。
选中name中包含字母ku的元素:
//Console.WriteLine(json_src.Select("*.[?(@.name == 'kun')]").ToString(4));
Console.WriteLine(json_src.Select("*.[?(@.name re '(?i)ku')]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Kun",
        "age": 26,
        "gender": 1,
        "hobby": [
            "sing", "dance", "rap", "basketball"
        ]
    }
]
(?i)中的i表示忽略大小写,其正则表达式以.Net中Regex为标准。(?...)开头则表示设置匹配模式。至于匹配模式自行查阅相关资料。
测试表达式
可能读者并不了解表达式在内部是如何被执行了并且会输出什么样的结果,作者提供了一个静态测试函数TestExpression()可用于调试表达式。若有什么不明白的地方测试一下就会看到过程及结果。
Console.WriteLine(STJsonPath.TestExpression(
    null,           // [STJson] 用于替代表达式中出现的 $
    null,           // [STJson] 用于替代表达式中出现的 @
    "1+2+3"         // 表达式文本
    ).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "type": "expression",
    "parsed": "{1 + 2 + 3}",        // 格式化后的文本 {}表示此部分需要单独执行 如: [1, {1+1}, 3]
    "polish": [
        "1", "2", " + ", "3", " + " // 逆波兰方式排列
    ],
    "steps": [                      // 执行步骤
        {
            "type": "excute",
            "operator": "+",
            "get_left_token": {     // 计算操作符左边元素的值,表达式左边也可能是一个表达式
                "parsed": "1",
                "type": "value",
                "result": {
                    "value_type": "Long",
                    "text": "1"
                }
            },
            "get_right_token": {
                "parsed": "2",
                "type": "value",
                "result": {
                    "value_type": "Long",
                    "text": "2"
                }
            },
            "result": {             // 该步骤执行结果
                "value_type": "Long",
                "text": "3"
            }
        }, {
            "type": "excute",
            "operator": "+",
            "get_left_token": {     // 此时操作符左边的元素为上一步的计算结果
                "parsed": "3",
                "type": "value",
                "result": {
                    "value_type": "Long",
                    "text": "3"
                }
            },
            "get_right_token": {
                "parsed": "3",
                "type": "value",
                "result": {
                    "value_type": "Long",
                    "text": "3"
                }
            },
            "result": {
                "value_type": "Long",
                "text": "6"
            }
        }
    ],
    "check_result": {               // 清空波兰表达式数据栈,确定最终输出结果。
        "parsed": "6",
        "type": "value",
        "result": {
            "value_type": "Long",
            "text": "6"
        }
    },
    "return": {                     // 最终返回值
        "value_type": "Long",
        "text": "6",
        "bool": true                // 如果用作布尔表达式则转换为 true
    }
}
如果过程不重要,仅仅是想看执行结果。
Console.WriteLine(STJsonPath.TestExpression(
    null,           // [STJson] 用于替代表达式中出现的 $
    null,           // [STJson] 用于替代表达式中出现的 @
    "1+2+3"         // 表达式文本
    ).SelectFirst("return").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "return": {                     // 最终返回值
        "value_type": "Long",
        "text": "6",
        "bool": true                // 如果用作布尔表达式则转换为 true
    }
}
ParsedToken
GetParsedTokens()用于获取当前STJsonPath得字符串在内部是如何被解析,且以STJson方式输出。如果你也想编写一个解析器,说不定可以给你提供一些思路。
Console.WriteLine(
    new STJsonPath("$..[?(matches(@.name,'u').count == 1)]").GetParsedTokens().ToString(2)
    );
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
  "type": "entry",
  "parsed": "[..]{matches({[@]['name']}, {'u'}) == 1}",
  "items": [
    {
      "type": "selector_item",
      "item_type": "Depth",
      "value": ".."
    }, {
      "type": "expression",
      "parsed": "{matches({[@]['name']}, {'u'}) == 1}",
      "items": [
        {
          "type": "function",
          "parsed": "matches({[@]['name']}, {'u'})",
          "name": "matches",
          "args": {
            "parsed": "({[@]['name']}, {'u'})",
            "items": [
              {
                "type": "expression",
                "parsed": "{[@]['name']}",
                "items": [
                  {
                    "type": "expression_item",
                    "item_type": "selector",
                    "items": [
                      {
                        "type": "selector_item",
                        "item_type": "Current",
                        "value": "@"
                      }, {
                        "type": "selector_item",
                        "item_type": "List",
                        "value": [
                          "name"
                        ]
                      }
                    ]
                  }
                ]
              }, {
                "type": "expression",
                "parsed": "{'u'}",
                "items": [
                  {
                    "type": "expression_item",
                    "item_type": "string",
                    "value": "u"
                  }
                ]
              }
            ]
          },
          "selector": {
            "parsed": "['count']",
            "items": [
              {
                "type": "selector_item",
                "item_type": "List",
                "value": [
                  "count"
                ]
              }
            ]
          }
        }, {
          "type": "expression_item",
          "item_type": "long",
          "value": 1
        }, {
          "type": "expression_item",
          "item_type": "operator",
          "value": "=="
        }
      ]
    }
  ]
}
其他功能
由于篇幅问题这里还有很多功能无法逐一介绍,读者可自行查看在线教程文档。比如数据聚合等功能。
 https://debugst.github.io/STJson/tutorial_cn.html
 


















