2023-06-06 -> by:DebugST

STLib.STJson

简介

STLib.STJson是一款基于MIT开源协议的Json解析库。由DebugST开发,该库纯原生实现不依赖任何库,所以非常轻量便捷,且功能强大。由于没用任何依赖纯原生构建,它可以很容易移植到其他编程语言中。当然由于作者懒的一批,这个概率不是很大。

在设计STJson的时候作者没有任何的异议,毕竟Json的数据格式不存在有争议的地方,当在设计STJsonPath的时候,作者犹豫了,因为作者发现很多存在争议的地方,且无法找到关于JsonPath的正式RFC文档,仅仅找到一份草案:

JSONPath: Query expressions for JSON

虽然草案中一些疑惑也能得到解决,但是作者仍有一些疑惑,毕竟不是正式的文档。在开发之前作者也好奇其他JsonPath的开源库,对于有争议的地方其他库是如何解决的。但很遗憾,问题依然存在。似乎没有库愿意搭理这份草案。既然JsonPath还没有正式的RFC文档,那么作者也想自立门户。但作者并不会不搭理这份草案,只能说作者会尽可能兼容草案的同时,还会加入一些作者自己的想法。

路劲疑问

JsonPath是源自于XPath的使用方式,总所周知XPathXML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。显而易见JsonPath是用于Json的。它们两种几乎使用方式相同,但是XMLJson两种数据格式的差异,必然会有存在不同的地方。

比如在XML的元素名中是不存在特殊字符的,可是JsonKey可以是任意的字符串,假如有一下Json数据:

{ "aa": { "bb": "cc" } }

JsonPath中我们可以通过路径aa.bb获取到值cc,这没有任何问题,如果Json数据换成下面的呢:

{ "a.a": { "bb": "cc" } }

很显然通过a.a.bb是无法得到值cc的。或许通常情况下Json都表示某个对象,而对象是不会存在这么奇怪的属性的。但是对于Json的数据格式而言,它的Key可以是任意字符。即便在浏览器中通过{OBJ_NAME}.a.a.bb也是只会得到报错。但是浏览器访问Json元素的方式并不是只用通过.的方式去获取,还有索引器{OBJ_NAME}['a.a']['bb']是可以得到正确值的。

STJsonPath中允许使用'或者",在词法分析器(STJsonPathTokenizer.cs)中会将其标记为String,若此字符串在非表达式的作用域内,解析器(STJsonPathParser.cs)会将其重新标记为Property作为索引使用,从而避免特殊字符串无法处理的情况,字符串支持\进行转义。所以在STJsonPath中可以使用'a.a'.bb获取到正确值。而在草案中也确实提到过类似的处理方式。

表达式疑问

在草案中有提到支持表达式,且表达式有两种类型,()?()。分别代表普通表达式过滤表达式。而按照作者的理解,普通表达式用于计算出一个值,且将这个值作为JsonPath的一部分,比如在很多案例中可以看到的$.books[(@.length - 1)]。在执行JsonPath的时候会可能对Json元素进行层层递归,其中@是一个动态变量,表示递归过程中当前正在处理的Json元素,根据表达式的表面意思是想获取books中的倒数第一个元素。

但是@.length如何被执行?length是哪里来的?如果@是一个数组,作者暂且可以理解为是在对数组求长度?可是如果@是一个对象呢?

既然是表达式,那么可以在()中写什么样的语法呢?语法规呢?关于这些疑问作者都采用了自己的实现方式,将在稍后的教程中介绍。

STJson API

数据类型关系

.NetSTJsonValueType.NetSTJsonValueType
byteLongsbyteLong
shortLongushortLong
intLonguintLong
longLongulongLong
floatDoubledoubleDouble
decimalDoubleboolBoolean
charStringstringString
DateTimeStringenumLong or String
PointArrayPointFArray
SizeArraySizeFArray
RectangleArrayRectangleFArray
ColorArrayDataTableObject
ArrayArrayICollectionArray
IDectionaryObjectobjectObject

STJson涵盖了常见的基本数据类型,即便不包含在其中,那么最后也会通过执行反射递归对象的属性。

静态函数

下列中(+n)表示有多个重载

returnsignaturenote
stringSerialize(+n)object对象序列化为字符串。
STJsonDeserialize(string)将字符串转换为STJson对象序。
TDeserialize<T>(+n)将对象或字符串转换为目标对象。
STJsonSTJson CreateObject()创建一个空白对象。
STJsonSTJson CreateArray(params object[])创建一个数组对象。
STJsonFromObject(+n)将一个对象转换为STJson
stringFormat(+n)格式化一个Json字符串。
voidAddCustomConverter(+n)自定义类型转换器。
voidRemoveCustomConverter(+n)移除自定义转换器。

非静态函数

returnsignaturenote
STJsonSetItem(+n)向对象中添加一个键值对,并返回自己。
STJsonSetKey(string)向对象中添加一个key,并返回目标对象。
voidSetValue(+n)设置目标对象的值。
STJsonDelete(string)从对象中移除一个key,并返回目标对象。
STJsonAppend(+n)向数组对象中添加一个或一些元素,并返回自己。
STJsonInsert(+n)向数组对象中插入一个元素,并返回自己。
STJsonRemoveAt(int nIndex)从数组对象中删除一个索引。
voidClear()清空所有子元素。
IEnumerator<STJson>GetEnumerator()获取当前元素中所有子元素。
STJsonClone克隆当前元素。

扩展函数

returnsignaturenote
STJsonSet(+n)根据路径(STJsonPath)设置对象。
stringGetValue(+n)获取对象的字符串值(仅值类型)
TGetValue<T>(+n)获取对象值(仅值类型)
STJsonSelect(+n)在对象中进行数据筛选。
STJsonSelectFirst(+n)在对象中进行数据筛选,并选中第一个结果。
STJsonSelectLast(+n)在对象中进行数据筛选,并选中最后一个结果。
STJsonGroup(+n)对指定字段进行分组。
STJsonTerms(+n)对指定字段的值进行个数统计。
STJsonSort(+n)对指定字段进行排序处理。
STJsonMin(+n)统计指定字段的最小值。
STJsonMax(+n)统计指定字段的最大值。
STJsonSum(+n)统计指定字段的总数。
STJsonAvg(+n)统计指定字段的平均值。

字段

typenamenote
stringKey当前STJson父元素的Key
objectValue当前STJson的值,若当前STJson不是值类型则为null
boolIsNullObject当前STJson是否为空元素,即无法确定当前STJson值的数据类型。
intCount当前STJson所包含子元素的个数。
STJsonValueTypeValueType(枚举)当前元素的数据类型。

STJsonValueType为以下值:

Undefined String Boolean Long Double Datetime Array Object

索引器

STJson [基本应用]

STJson是一个中间数据类型,它是stringobject之间的桥梁,使用非常便捷,比如:

var st_json = new STJson()
    .SetItem("number", 0)               // 函数返回自身 所以可以连续操作
    .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 -> indentation space count
/*******************************************************************************
 *                                [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"
}

当执行var st_json = new STJson()时,st_json为空元素,即st_json.IsNullObject = true。因为此时无法确定st_json对象还是数组或者是

STJson中不存在类似于JArrayJObject对象,STJson既可以是Array也可以是ObjectSTJson拥有两个索引器[int][string]

var json_1 = new STJson();
Console.WriteLine("[json_1] - " + json_1.IsNullObject + " - " + json_1.ValueType);

var json_2 = new STJson();
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 - None
[json_2] - False - Object
[json_3] - False - Array
[json_4] - False - Datetime
[json_5] - False - Array
[json_6] - False - Object
[json_7] - False - Long

ValuteType = Array时,无法调用SetItem(+n),当ValueType = Object时,无法调用Append(+n)。但是通过SetValue(+n)可强制改变ValueType

正如上面提到的STJson有两个索引器,可以通过索引器访问他们,或者获取值。

var json_temp = STJson.CreateArray()
    .SetItem("string", "this is string")
    .SetItem("array", new Object[] { "1", "2", "3" });
Console.WriteLine(json_temp["string"]);
Console.WriteLine(json_temp["string"].GetValue());
Console.WriteLine(json_temp["array"][1]);
Console.WriteLine(json_temp["array"][1].GetValue<long>());
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
"this is string"
this is string
"2"
2

object -> string

通过上面的例子或许你已经知道怎么将一个对象转换为string,通过STJson.FromObject(object).ToString(+n)即可,但是有没有可能,其实不用这么麻烦的?比如:STJson.Serialize(+n)就可以了???

事实上STJson.Serialize(+n)的效率会更好,因为它是直接将对象转换为字符串,而不是转换成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(+n)完成的。如果你觉得不喜欢作者内置的格式化风格,完全可以自己写一个格式化的函数。或者去修改源码文件STJson.Statics.cs:Format(string,int)

string -> object

事实上代码并不会直接将string转换为object。因为在那之前必须先对字符串进行解析,确保它是一个正确格式的Json。但是做完这个过程的时候已经得到一个STJson对象了。最后将STJson再转换为object

所以你会在源代码STLib.Json.Converter中看到如下文件:

ObjectToSTJson.cs ObjectToString.cs STJsonToObject.cs StringToSTJson.cs

里面并没有StringToObject.cs文件,而STJson.Deserialize(+n)的源码如下:

public static T Deserialize<T>(string strJson, +n) {
    var json = StringToSTJson.Get(strJson, +n);
    return STJsonToObject.Get<T>(json, +n);
}

STJson -> object

如何将字符串转换为对象,相信作者不用说明读者也应该知道如何处理,但是这里值得说明的是,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

虽然在STJson中内置了很多数据类型的转换,即便没有的数据类型也会被当做object做递归处理。但是有时情况并不是很友好。比如:

Rectangle rect = new Rectangle(10, 10, 100, 100);
Console.WriteLine(STJson.Serialize(rect, 4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "Location": {
        "IsEmpty": false,
        "X": 10,
        "Y": 10
    },
    "Size": {
        "IsEmpty": false,
        "Width": 100,
        "Height": 100
    },
    "X": 10,
    "Y": 10,
    "Width": 100,
    "Height": 100,
    "Left": 10,
    "Top": 10,
    "Right": 110,
    "Bottom": 110,
    "IsEmpty": false
}

很显然,这个结果过于复杂,因为Rectangle的所有字段都被递归出来了。但是,如果这样呢?

public class RectangleConverter : STJsonConverter
{
    public override object JsonToObject(Type t, STJson json, ref bool bProcessed) {
        return new Rectangle(
            json["x"].GetValue<int>(),
            json["y"].GetValue<int>(),
            json["w"].GetValue<int>(),
            json["h"].GetValue<int>());
    }

    public override STJson ObjectToJson(Type t, object obj, ref bool bProcessed) {
        Rectangle rect = (Rectangle)obj;
        return STJson.New()
            .SetItem("x", rect.X)
            .SetItem("y", rect.Y)
            .SetItem("w", rect.Width)
            .SetItem("h", rect.Height);
    }

    public override string ObjectToString(Type t, object obj, ref bool bProcessed) {
        //return "{\"x\":" + ... + "}"
        var json = this.ObjectToJson(t, obj, ref bProcessed);
        if (bProcessed) {
            return json.ToString();
        }
        return null;
    }
}

Rectangle rect = new Rectangle(10, 10, 100, 100);
STJson.AddCustomConverter(typeof(Rectangle), new RectangleConverter());
string strResult = STJson.Serialize(rect);
Console.WriteLine(strResult);
rect = STJson.Deserialize<Rectangle>(strResult.Replace("100", "200"));
Console.WriteLine(rect);
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{"x": 10,"y": 10,"w": 100,"h": 100}
{X=10,Y=10,Width=200,Height=200}

其中bProcessed默认传入值为true,当上一层函数获取到false时则使用默认的处理方式。

STJsonConverter提供了Attribute类,也可用于标记对象属性。

public class Test{
    [STJsonConverter(typeof(RectangleConverter))]
    public Rectangle Rect{get; set;}
}
STJsonConverter.cs
public abstract class STJsonConverter
{
    public virtual STJson ObjectToJson(Type t, object obj, ref bool bProcessed) {
        bProcessed = false;
        return null;
    }
    public virtual string ObjectToString(Type t, object obj, ref bool bProcessed) {
        bProcessed = false;
        return null;
    }
    public virtual object JsonToObject(Type t, STJson json, ref bool bProcessed) {
        bProcessed = false;
        return null;
    }
}

STJsonAttribute

或许在序列化的时候你并不想输出所有的属性,那么可以通过STJsonAttribute去控制。

[STJson(STJsonSerilizaMode.Include)]    // optional
public class Student
{
    [STJsonProperty("test_name")]
    public string Name;
    public int Age;
    public Gender Gender;
    [STJsonProperty]                        // optional
    public List<string> Hobby;
}

public enum Gender
{
    Male, Female
}

var stu = new Student() {
    Name = "Tom",
    Age = 100,
    Gender = Gender.Male,
    Hobby = new List<string>() { "Game", "Sing" }
};

str = STJson.Serialize(stu);
Console.WriteLine(str);
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{"test_name":"Tom","Hobby":["Cooking","Sports"]}

STJsonSetting

STJsonSetting用于在序列化或者反序列化中添加一些个性化设置。原本设置是全局的。但随着一些设置项的增多,作者认为全局设置粘性太高,所以独立出来STJsonSetting用于解耦。同时独立的设置类也可以方便后续版本的功能扩展,当然后续版本的概率不是很大。除非作者不想当咸鱼了。

var stu = new Student() {
    Name = "Tom",
    Age = 100,
    Gender = Gender.Male,
    Hobby = new List<string>() { "Game", "Sing" }
};
STJsonSetting setting = new STJsonSetting();
setting.EnumUseNumber = true;
setting.IgnoreAttribute = true;
setting.Mode = STJsonSettingKeyMode.Exclude;
setting.KeyList.Add("Age");
str = STJson.Serialize(stu, setting);
Console.WriteLine(STJson.Format(str));
STJson.Deserialize<Student>(str);
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "test_name": "Tom",
    "Gender": 0,
    "Hobby": [
        "Game", "Sing"
    ]
}

Attribute的优先级大于STJsonSetting

json_src

在接下来的教程中我们会使用到一些测试数据,数据如下:

test.json
[{
    "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

选择器

tokennote
$根节点选择器,可视作代表根节点对象。
@当前元素选择器,在遍历过程中指代当前被遍历的元素。
*通配符,表示可以代表任何一个节点。
.<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来说并不是必须的,且内部会移除掉开头的$或者@$@仅在表达式中作为对象的变量使用。

在表达式中选择器的返回值仅返回选中的第一个结果。而不是数组列表,这点将在后面说明。

STJsonPath中允许使用'或者",比如:'a.b' "a.b" STJsonPath会将其视为一个独立的个体。而不是两个。列如有如下Json

{
    "a.b": "this is a test"
}

很明显通过Select("a.b")是无法获取到数据的,需要通过Select("'a.b'")

string strTemp = "{\"a.b\": \"this is a test\"}";
var json_temp = STJson.Deserialize(strTemp);
Console.WriteLine(json_temp.Select("a.b"));
Console.WriteLine(json_temp.Select("'a.b'"));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[]
["this is a test"]

在字符串中支持\进行转义:\r\n\t\f\b\a\v\0\x..\u...\.

通配符

通配符可表示当前层级中的任何一个节点。获取所有人员姓名。

Console.WriteLine(json_src.Select("*.name").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "Tom", "Tony", "Andy", "Kun"
]

深度选择器

深度选择器与通配符类似,但深度选择器可以是任意层级。

Console.WriteLine(json_src.Select("..name").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "Tom", "Tony", "Andy", "Kun"
]

列表选择器

列表选择器支持intstring两种方式,虽然在上面选择器的表格中列举了两个列表选择器,但是在STJsonPath中只有一个列表选择器,它们可以混合使用,比如下面的使用方式都是合法的:

STJsonPath在内部会自动拆分为两个列表选择器,并判断STJsonValueType决定使用哪个列表选择器。内部实现代码如下:

case STJsonPathItem.ItemType.List:
    if (jsonCurrent.ValueType == STJsonValueType.Object) {
        foreach (var v in item.Keys) {
            if (jsonCurrent[v] == null) {
                continue;
            }
            // ...
        }
    }
    if (jsonCurrent.ValueType == STJsonValueType.Array) {
        foreach (var v in item.Indices) {
            nIndexSliceL = v;
            if (nIndexSliceL < 0) nIndexSliceL = jsonCurrent.Count + nIndexSliceL;
            if (nIndexSliceL < 0) continue;
            if (nIndexSliceL >= jsonCurrent.Count) continue;
            // ...
        }
    }
    break;

选择索引为02的元素。

Console.WriteLine(json_src.Select("[0,2]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Tom",
        "age": 16,
        "gender": 0,
        "hobby": [
            "cooking", "sing"
        ]
    }, {
        "name": "Andy",
        "age": 20,
        "gender": 1,
        "hobby": [
            "draw", "sing"
        ]
    }
]

对于int索引可以使用负数,比如-1则表示获取最后一个元素。当STJsonPath检测到负数时候会执行STJson.Count - n将结果作为索引。

//Console.WriteLine(json_src.Select("-1").ToString(4));
Console.WriteLine(json_src.Select("[-1]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Kun",
        "age": 26,
        "gender": 1,
        "hobby": [
            "sing", "dance", "rap", "basketball"
        ]
    }
]

切片选择器

切片选择器用于在数组中选择一个片段,切片选择器默认值[0:-1:1],在切片选择器内部如下实现:

case STJsonPathItem.ItemType.Slice:
    if (jsonCurrent.ValueType != STJsonValueType.Array) {
        return;
    }
    if (nIndexSliceL < 0) nIndexSliceL = jsonCurrent.Count + nIndexSliceL;
    if (nIndexSliceR < 0) nIndexSliceR = jsonCurrent.Count + nIndexSliceR;
    if (nIndexSliceL < 0) nIndexSliceL = 0;
    else if (nIndexSliceL >= jsonCurrent.Count) nIndexSliceL = jsonCurrent.Count - 1;
    if (nIndexSliceR < 0) nIndexSliceR = 0;
    else if (nIndexSliceR >= jsonCurrent.Count) nIndexSliceR = jsonCurrent.Count - 1;
    if (nIndexSliceL > nIndexSliceR) {
        for (int i = nIndexSliceL; i >= nIndexSliceR; i -= item.Step) {
            // ...
        }
    } else {
        for (int i = nIndexSliceL; i <= nIndexSliceR; i += item.Step) {
            // ...
        }
    }
    break;

所以切片中的三个值等同于for循环中的三个条件,所以原理与效果就不再说明。

expressionrangenote
[::]0 <= R <= (OBJ).length - 1等同于*
[5:]5 <= R <= {OBJ}.length - 1从第6个元素开始,获取所有元素
[-1:0]{OBJ}.length - 1 >= R >= 0倒序获取数据
[0::2]0 <= R <= {OBJ}.length - 1顺序获取数据,且间隔一个数据

切片选择器中至少出现一个:step大于0,否则将获得异常。

Console.WriteLine(json_src.Select("[-1:]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Kun",
        "age": 26,
        "gender": 1,
        "hobby": [
            "sing", "dance", "rap", "basketball"
        ]
    }
]

表达式

[?()]中可支持下列运算符,优先级从上至下依次升高


operatornotee.g
re正则表达式[?(@.name re 'un')]
in左边的值或数组包含在右边的数组中[?(@.age in [16,20])]
nin左边的值或数组不包含在右边的数组中[?(@.hobby nin ['sing','draw'])]
anyof左边的值或数组和右边的数组存在交集[?(@.hobby anyof ['sing','draw'])]

表达式有两种模式:

过滤表达式

选中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表示忽略大小写,其正则表达式以.NetRegex为标准。(?...)开头则表示设置匹配模式。至于匹配模式自行查阅相关资料。

选中hobby不包含singswing的元素:

Console.WriteLine(json_src.Select("*.[?(@.hobby nin ['sing','draw'])]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Tony",
        "age": 16,
        "gender": 0,
        "hobby": [
            "game", "dance"
        ]
    }
]

普通表达式

普通表达式会将结果作为STJsonPath的部分继续匹配。

Console.WriteLine(json_src.Select("*.[('na' + 'me')]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "Tom", "Tony", "Andy", "Kun"
]

[('na' + 'me')]'na' + 'me'的结果为'name',并且会将这个值作为索引,所以上述效果等同于*.name,当然返回值也可以是一个集合。

Console.WriteLine(json_src.Select("*.[(['na' + 'me', 'age', 0, 1 + 1])]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "Tom", 16, "Tony", 16, "Andy", 20, "Kun", 26
]

上面表达式的结算结果值为['name', 'age', 0, 2]。但是很显然02将不会起到任何作用,因为第二层的数据对象并不是一个数组。

上面的表达式等同于*.['name', 'age', 0, 2]。如果将上面的换成第三层会得到下面的结果。

Console.WriteLine(json_src.Select("*.*.[(['na' + 'me', 'age', 0, 1 + 1])]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "cooking", "game", "draw", "sing", "rap"
]

可以看到'name''age'对于hobby来说是无效的,因为hobby是一个数组。

测试表达式

可能读者并不了解表达式在内部是如何被执行了并且会输出什么样的结果,作者提供了一个静态测试函数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
    }
}

内置函数

returnsignaturenote
stringtypeof(+n)获取数据类型。
stringstr(+n)转换为字符串。
stringupper(+n)转换为大写。
stringlower(+n)转换为小写。
longlen(+n)获取字符串或者数组长度。
longlong(+n)转换为整数。
doubledouble(+n)转换为浮点数。
long or doubleabs(+n)获取绝对值。
longround(+n)四舍五入。
longceil(+n)向上取整。

typeof具有下列返回值:

string long double boolean array object undefined

以上函数可使用如下签名

其中bool表示是否期望以数组方式获得结果,如果为true则以数组方式输出结果,并且当object为数组是,递归其中的数据。

Console.WriteLine(STJsonPath.TestExpression(
    null,
    null,
    "typeof([1+2+3,'abc'], false)"
    ).SelectFirst("return").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "bool": true,
    "value_type": "String",
    "value": "array"
}
// =============================================================================
Console.WriteLine(STJsonPath.TestExpression(
    null,
    null,
    "typeof([1+2+3,'abc'], true)"
    ).SelectFirst("return").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "bool": true,
    "value_type": "Array",
    "items": [
        "long", "string"
    ]
}

不同的函数第二个参数采用不同的默认值。一下是一些其他函数,读者可以自己通过TestExpression()进行效果测试。

returnsignaturenote
long or doublemax(+1)求最大值。
long or doublemin(+1)求最小值。
long or doubleavg(+1)求平均值。
long or doublesum(+1)求总和。
stringtrim(+n)裁切字符串两端的指定字符。
stringtrims(+n)裁切字符串开始的指定字符。
stringtrime(+n)裁切字符串末尾的指定字符。
string_arraysplit(+1)拆分字符串。
long or stringtime(+n)获取或格式化时间戳。

内置函数列表

随着版本的迭代更新(如果可能的话),内置函数可能随时发生变化,通过GetBuildInFunctionList()可以查看当前版本支持的内置函数信息。

Console.WriteLine(STJsonPath.GetBuildInFunctionList().ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "typeof",
        "demos": [
            "(object) -> typeof('abc')", "(array,bool) -> typeof(['abc',123],true)"
        ]
    },
    ...
]

读者可以通过TestExpression()按照demos对函数进行测试。

选中hobby长度大于2的元素:

Console.WriteLine(json_src.Select("..[?(len(@.hobby) > 1 + 1)]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Kun",
        "age": 26,
        "gender": 1,
        "hobby": [
            "sing", "dance", "rap", "basketball"
        ]
    }
]

自定义函数

在为表达式提供内置函数的时候作者也不知道开发者期望内置什么样的函数,随便内置了几个之后干脆不想了。没有什么问题是写代码解决不了的,没错。。。让开发者自己去写。但是开发者要怎么样为STJsonPath提供函数呢?

STJsonPath.CustomFunctions是一个静态字典。用于保存开发者自定义的函数。其函数签名如下:

public delegate STJson STJsonPathCustomFuncHander(STJsonPathExpFuncArg[] args);

public struct STJsonPathExpFuncArg
{
    public STJsonPathExpFuncArgType Type;
    public object Value;
}

public enum STJsonPathExpFuncArgType
{
    Undefined, Number, String, Boolean, Array, Object,
}

自定义函数返回值统一STJson,因为STJson可与STJsonPath完美衔接,在STJsonPath可直接使用选择器对自定义函数返回值进行操作。比如下面的案例为STJsonPath添加一个matches函数用于正则表达式操作。

STJsonPath.CustomFunctions.Add("matches", (objs) => {
    var json_ret = new STJson();
    json_ret.SetItem("count", 0);
    json_ret.SetItem("values", STJson.CreateArray());
    if (objs.Length != 2) {
        return json_ret;
    }
    var ms = Regex.Matches(objs[0].Value.ToString(), objs[1].Value.ToString());
    json_ret["count"].SetValue(ms.Count);
    foreach (Match v in ms) {
        json_ret["values"].Append(STJson.FromObject(new {
            success = v.Success,
            index = v.Index,
            length = v.Length,
            value = v.Value
        }));
    }
    return json_ret;
});

然后我们需要筛选出hobby中包含两个ao的元素:

Console.WriteLine(json_src.Select("*.hobby.*[?(matches(@,'a|o').count == 2)]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "cooking", "basketball"
]

自定义函数优先级高于内置函数,也就是说如果在自定义函数中和内置函数中出现了同名函数,则优先调用自定义函数

表达式中的选择器

在表达式中的选择器仅返回选中的第一个结果,而不是数组列表。

Console.WriteLine(json_src[0].Select("name").ToString(4));
Console.WriteLine(STJsonPath.TestExpression(json_src[0], json_src[0], "@.name").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "Tom"
]
{
    "type": "expression",
    "parsed": "{[@]['name']}",
    "polish": [
        "[@]['name']"
    ],
    "steps": [

    ],
    "check_result": {
        "parsed": "[@]['name']",
        "type": "selector",
        "root_json": "{\"name\":\"Tom\",\"age\":16,\"gender\":0,\"hobby\":[\"cooking\",\"sing\"]}",
        "current_json": "{\"name\":\"Tom\",\"age\":16,\"gender\":0,\"hobby\":[\"cooking\",\"sing\"]}",
        "selected_json": "[\"Tom\"]",
        "result": {                     // 返回值将只获取第一个结果
            "value_type": "String",
            "text": "Tom"
        }
    },
    "return": {
        "bool": true,
        "value_type": "String",
        "text": "Tom"
    }
}

选择方式

普通模式(默认方式):

Console.WriteLine(json_src.Select("..name", STJsonPathSelectMode.ItemOnly).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "Tom", "Tony", "Andy", "Kun"
]

路径模式:

Console.WriteLine(json_src.Select("..name", STJsonPathSelectMode.ItemWithPath).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "path": [
            0, "name"
        ],
        "item": "Tom"
    }, {
        "path": [
            1, "name"
        ],
        "item": "Tony"
    }, {
        "path": [
            2, "name"
        ],
        "item": "Andy"
    }, {
        "path": [
            3, "name"
        ],
        "item": "Kun"
    }
]

保持结构:

Console.WriteLine(json_src.Select("..name", STJsonPathSelectMode.KeepStructure).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Tom"
    }, {
        "name": "Tony"
    }, {
        "name": "Andy"
    }, {
        "name": "Kun"
    }
]

ParsedTokens

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": "=="
        }
      ]
    }
  ]
}

STJson [高级应用]

获取值

试想一个场景,作为一个WEB后端服务,需要处理前端提交过来的Json数据,假定我们期望得到以下Json数据:

{
    "type": "get_list",
    "page": {
        "from": 100,
        "size": 10
    },
    "other": {}
}

并且我们已经将上面的数据转化为STJson且命名为json_post。其中fromsize用于翻页功能。那么后台可能会做如下判断:

if(json_post["page"] == null) { /* do something */ }
if(json_post["page"]["from"] == null) { /* do something */ }
if(json_post["page"]["from"].ValueType != STJsonValueType.Long) {
    /* do something */
}
int nFrom = json_post["page"]["from"].GetValue<int>();
// 或者 直接暴力一点
int nFrom = 0;
try{
    nFrom = json_post["page"]["from"].GetValue<int>();
}catch{
    /* do something */
}

很显然上面的代码让你抓狂。。。当然为了减少麻烦可以在后端代码中为其创建一个实体对象,然后将Json绑定到实体对象中。。。但是为每个Post过来的数据类型创建一个实体对象是不是也挺麻烦的。但是如果使用下面的代码。

int nFrom = json_post.GetValue<int>("page.from");

或许你会疑惑,,如果路径不存在或者from根本不是数字怎么办?emm....上面的代码依然会报错,因为上面的代码内部调用是:

public static T GetValue<T>(this STJson json, string strJsonPath) {
    return json.GetValue<T>(new STJsonPath(strJsonPath));
}

public static T GetValue<T>(this STJson json, STJsonPath jsonPath) {
    var j = jsonPath.SelectFirst(json);
    if (j == null) {
        throw new STJsonPathException("Can not selected a object with path {" + jsonPath.SourceText + "}");
    }
    var t = typeof(T);
    bool bProcessed = true;
    var convert = STJsonBuildInConverter.Get(t);
    if (convert != null) {
        var value = convert.JsonToObject(t, json, ref bProcessed);
        if (bProcessed) {
            return (T)value;
        }
    }
    return (T)j.Value;
}

因为当不存在元素时候,不知道需要返回什么值,可能读者认为无法获取值,返回一个默认值就好了,的确可以这么设计,可是调用者如何确定返回的值是真实的值还是默认值。无法确定是否异常了。除非强制指定一个默认值。

int nFrom = json_post.GetValue<int>("page.from", 0);

如果存在元素且能正常转换则返回元素值,否则返回0

或者想知道是否返回的是真实值:

int nFrom = 0;
var bFlag = json_src.GetValue<int>("page.from", out nFrom);

若填写的strJsonPath会获取到多个值,则取第一个值。

设置值

如果我们需要手动构造一个Json可以通过STJson.SetItem()来添加元素。可是有些场景这样使用似乎有点麻烦,比如以ElasticSearch数据库的query语法为例。我们需要构造一个下面的数据:

{
    "query":{
        "term":{
            "field_name": "field_value"
        }
    }
}

那么按照之前的写法我们需要这样去构造一个STJson对象:

var json = new STJson()
    .SetItem("query", new STJson()
        .SetItem("term", new STJson()
            .SetItem("field_name", "field_value"))
        );
// or
var json = STJson.FromObject(new {
    query = new {
        term = new {
            field_name = "field_value"
        }
    }
});

但是还可以这样写代码:

var json = new STJson().Set("query.term.field_name", "field_value");
Console.WriteLine(json.ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "query":{
        "term":{
            "field_name": "field_value"
        }
    }
}

甚至是这样:

var json = new STJson().Set("array[0:4].[key_1,key_2]", "value");
Console.WriteLine(json.ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "array": [
        {
            "key_1": "value",
            "key_2": "value"
        }, {
            "key_1": "value",
            "key_2": "value"
        }, {
            "key_1": "value",
            "key_2": "value"
        }, {
            "key_1": "value",
            "key_2": "value"
        }, {
            "key_1": "value",
            "key_2": "value"
        }
    ]
}

Set中仅仅支持列表选择器切片选择器。不支持其他选择器。

回调函数

通过上面的Set我们是否能够将json_src中每个人的喜好都添加一个coding?。。似乎不太可以。毕竟向hobby中添加数据需要append而不是set

当然也并不是没有办法。

json_src.Select("*.hobby", (p, j) => {
    j.Append("coding");
});
Console.WriteLine(json_src.ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Tom",
        "age": 16,
        "gender": 0,
        "hobby": [
            "cooking", "sing", "coding"
        ]
    },
    ...
]

Select中支持两种回调函数,一个需要返回值,另一个不需要返回值,如同上面的。而下面的带返回值。

var json = json_src.Select("*.hobby.*", (p, j) => {
    return new STJsonPathCallBackResult() {
        Selected = true,            // 是否将此条 json 添加到结果中
        Json = new STJson()         // 需要被添加到结果的 json
            .SetItem("path", p)
            .SetItem("item", j.Value.ToString().ToUpper())
    };
});
Console.WriteLine(json.ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "path": [
            0, "hobby", 0
        ],
        "item": "COOKING"
    },
    ...
]

PathItem

STJsonPath中有一个比较特殊的数据结构。

[
    {
        "path": [
            ...
        ],
        "item": ...
    },
    ...
]

可以通过RestorePathJson()还原其结构。

var json = json_src.Select("*.hobby.*", (p, j) => {
    return new STJsonPathCallBackResult() {
        Selected = true,
        Json = new STJson()
            .SetItem("path", p)
            .SetItem("item", j.Value.ToString().ToUpper())
    };
});
Console.WriteLine(STJsonPath.RestorePathJson(json).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "hobby": [
            "COOKING", "SING"
        ]
    }, {
        "hobby": [
            "GAME", "DANCE"
        ]
    }, {
        "hobby": [
            "DRAW", "SING"
        ]
    }, {
        "hobby": [
            "SING", "DANCE", "RAP", "BASKETBALL"
        ]
    }
]

Clone

如果想向所有的用户喜好中添加coding。但是又不想影响源数据,那么可以克隆一份数据进行操作。

json_src.Clone().Select("*.hobby", (p, j) => {
    j.Append("coding");
});
Console.WriteLine(json_src.ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Tom",
        "age": 16,
        "gender": 0,
        "hobby": [
            "cooking", "sing"
        ]
    },
    ...
]

数据聚合

STJson中内置了部分扩展函数用于聚合操作,进行一些简单的数据处理。结合STJsonPath可以快速且简单的完成一些数据操作。

sort

Sort()用于对数据进行排序,内部采用归并排序方式。此函数有如下形式签名:

returnsignaturenote
STJsonSort()不指定path进行排序。默认升序。
STJsonSort(bool)不指定path进行排序,指定是否为降序。
STJsonSort(params object[])用于指定path或是否降序。

不指定path进行排序:

var arr_obj = new object[] { 4, 2, 5, 6, 1, true, null, new { aa = "aa" } };
var json_objs = STJson.FromObject(arr_obj);
Console.WriteLine(json_objs.Sort());
Console.WriteLine(json_objs.Sort(true));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[true,1,2,4,5,6,{"aa":"aa"},null]
[6,5,4,2,true,1,{"aa":"aa"},null]

你是否会感到疑惑为什么会是这样的结果?。。。但是在那之前为什么你不先疑惑一下为什么要用这样的数组去排序呢?

Sort的内部会对每个元素获取出一个数字并记录元素索引,然后对这个数字进行排序,最后根据索引位置重新组合一个数组达到排序目的。对于如何获取元素的数字规则如下:

switch (item.ValueType) {
    case STJsonValueType.Long:
    case STJsonValueType.Double:
        d_temp = item.GetValue<double>();
        break;
    case STJsonValueType.Boolean: // true 被计算成 1
        d_temp = item.GetValue<bool>() ? 1 : 0;
        break;
    case STJsonValueType.Datetime:
        d_temp = Convert.ToDouble(item.GetValue<DateTime>());
        break;
    case STJsonValueType.String:
        d_temp = item.Value == null ? 0 : item.Value.ToString().Length;
        break;
    default:    // 对于其他类型让其始终排在最后面即可
        d_temp = arr_b_desc[j] ? double.MinValue : double.MaxValue;
        break;
}

所以如果你没有那么奇怪的数组的话,那么一切都会正常起来。如果实在是想只对里面的数字排序怎么办?

var arr_obj = new object[] { 4, 2, 5, 6, 1, true, null, new { aa = "aa" } };
var json_objs = STJson.FromObject(arr_obj)
    .Select("..[?(typeof(@) in ['long', 'double'])]");
Console.WriteLine(json_objs.Sort());
Console.WriteLine(json_objs.Sort(true));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[1,2,4,5,6]
[6,5,4,2,1]

指定path进行排序:

Console.WriteLine(json_src.Sort("age").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Tony",
        "age": 16,
        "gender": 0,
        "hobby": [
            "game", "dance"
        ]
    }, {
        "name": "Tom",
        "age": 16,
        "gender": 0,
        "hobby": [
            "cooking", "sing"
        ]
    }, {
        "name": "Andy",
        "age": 20,
        "gender": 1,
        "hobby": [
            "draw", "sing"
        ]
    }, {
        "name": "Kun",
        "age": 26,
        "gender": 1,
        "hobby": [
            "sing", "dance", "rap", "basketball"
        ]
    }
]

当然你也可以指定降序排列:

Console.WriteLine(json_src.Sort("age", true).ToString(4));

还可以同时指定多个字段进行排序:

Console.WriteLine(json_src.Sort("gender", false, "age", true).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "name": "Tony",
        "age": 16,
        "gender": 0,
        "hobby": [
            "game", "dance"
        ]
    }, {
        "name": "Tom",
        "age": 16,
        "gender": 0,
        "hobby": [
            "cooking", "sing"
        ]
    }, {
        "name": "Kun",
        "age": 26,
        "gender": 1,
        "hobby": [
            "sing", "dance", "rap", "basketball"
        ]
    }, {
        "name": "Andy",
        "age": 20,
        "gender": 1,
        "hobby": [
            "draw", "sing"
        ]
    }
]

以上为使用gender进行升序,然后对于相同值再对age进行降序排列。Sort函数参数规则如下:

Sort(string strJsonPath, bool isDesc[, string strJsonPath, bool isDesc]*);
// STLib.Json.STJsonExtension.cs:Sort()
public static STJson Sort(this STJson json, params object[] fields) {
    var jsonSort = STJson.CreateArray();
    for (int i = 0; i < fields.Length; i += 2) {
        var j = new STJson()
            .SetItem("path", fields[i] == null ? null : fields[i].ToString())
            .SetItem("desc", i + 1 < fields.Length ? Convert.ToBoolean(fields[i + 1]) : false);
        jsonSort.Append(j);
    }
    return json.Sort(jsonSort);
}

group

Group()用于对数据指定path进行分组。使用方式如下:

Console.WriteLine(json_src.Group("gender").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "gender": [
        {
            "value": 0,
            "items": [
                {
                    "name": "Tom",
                    "age": 16,
                    "gender": 0,
                    "hobby": [
                        "cooking", "sing"
                    ]
                }, {
                    "name": "Tony",
                    "age": 16,
                    "gender": 0,
                    "hobby": [
                        "game", "dance"
                    ]
                }
            ]
        }, {
            "value": 1,
            "items": [
                {
                    "name": "Andy",
                    "age": 20,
                    "gender": 1,
                    "hobby": [
                        "draw", "sing"
                    ]
                }, {
                    "name": "Kun",
                    "age": 26,
                    "gender": 1,
                    "hobby": [
                        "sing", "dance", "rap", "basketball"
                    ]
                }
            ]
        }
    ]

同样的你也可以使用多个path进行分组,但是和Sort不同的是这些path不是对第一个path的结果再进行分组。是并列的,而非嵌套的。

Console.WriteLine(json_src.Group("gender", "age").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "gender": [...],
    "age": [...]
}

如果想进行嵌套Group()应该怎么办?作者并不打算在Group()中实现,因为STJsonPath可以完成这个工作。

terms

Terms()ElasticSearch数据库中的聚合类似,统计某个字段的出现次数,与Sort()一样,它可以选择是否指定path或者多个path

Console.WriteLine(json_src.Terms("hobby", "gender").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "hobby": [
        {
            "value": "cooking",
            "count": 1
        }, {
            "value": "sing",
            "count": 3
        }, {
            "value": "game",
            "count": 1
        }, {
            "value": "dance",
            "count": 2
        }, {
            "value": "draw",
            "count": 1
        }, {
            "value": "rap",
            "count": 1
        }, {
            "value": "basketball",
            "count": 1
        }
    ],
    "gender": [
        {
            "value": 0,
            "count": 2
        }, {
            "value": 1,
            "count": 2
        }
    ]
}

如你所见,似乎hobby并没有对count进行排序啊?是的。。。这是作者故意的。。。咋地?不服?。。。有没有可能其实Terms()还有一个作用?在ES数据库中有个cardinality。用作统计某字段去重后的数据个数。

有没有可能其实作者也想实现一个这样的函数?。。但是这个函数写到一半的时候,作者当场Delete。。。没必要啊。。上面的Terms()不就已经完成了这个工作了吗?比如:

json_src.Terms("hobby")["hobby"].Count;

而且有没有一种可能cardinality只统计个数。。。但是。。如果想要获取到去重后的字段值都有哪些要怎么办?。。。而刚才说的这些情况Terms()似乎都已经完成了。。仅仅是没有做排序。都已经有Sort()函数了,再单独排个序怎么了?

json_src.Terms("hobby")["hobby"].Sort("count", true);

如果想获得人员最受欢迎的前三个喜好要怎么做?

Console.WriteLine(
    json_src.Terms("hobby")["hobby"]
    .Sort("count", true)
    .Select("[0:3]")
    .Select("*.value")
    .ToString(4)
    );
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    "sing", "dance", "basketball"
]

min, max

Min() Max()分别为寻找最小值和最大值元素,可以不指定或者指定多个path

Console.WriteLine(json_src.Min("age").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "age": {
        "count": 4,                 // 其中 4 个元素参与了计算
        "value": 16,                // 最小的 age 值为 16
        "items": [                  // 满足最小值的元素
            {
                "name": "Tom",
                "age": 16,
                "gender": 0,
                "hobby": [
                    "cooking", "sing"
                ]
            }, {
                "name": "Tony",
                "age": 16,
                "gender": 0,
                "hobby": [
                    "game", "dance"
                ]
            }
        ]
    }
}

其实上面的效果有点像Group有点像了,再加上一个Sort()。。简直就是一模一样。。。连作者自己都懵逼了。。。就应该在Min/Max()内部使用Group() + Sort()实现的。。算了算了。。代码都写完了。但是很显然Sort()必然会降低效率。。但是话又说回来。。小数据量不在乎效率。。但是大数据量呢???大数据量你给我说你用Json数组保存????你食不食油饼???

avg, sum

Avg() Sum()分别用于计算平均值和汇总,可以不指定或者指定多个key

Console.WriteLine(json_src.Avg("age").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "age": {
        "count": 4,         // 其中 4 个元素参与了计算
        "value": 19.5       // 平均值 19.5
    }
}

Sum()返回值与Avg()一致,唯一不同的是,value一个是平均值,一个是汇总。

STJsonPath.Name

在上面的演示中我们有使用指定一个字段进行数据操作,在结果的json中会以这个字段名称作为一个key输出。但是上面并没有强调是在指定key进行操作,而是path。只是路径比较简单让我们看起来像是key

如果这样进行聚合呢?

Console.WriteLine(json_src.Terms("hobby[0]").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "hobby[0]": [
        {
            "value": "cooking",
            "count": 1
        }, {
            "value": "game",
            "count": 1
        }, {
            "value": "draw",
            "count": 1
        }, {
            "value": "sing",
            "count": 1
        }
    ]
}

好像看上去很奇怪的样子。其实STJsonPath有一个Name属性。

Console.WriteLine(json_src.Terms(new STJsonPath("test", "hobby[0]")).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "test": [
        {
            "value": "cooking",
            "count": 1
        }, {
            "value": "game",
            "count": 1
        }, {
            "value": "draw",
            "count": 1
        }, {
            "value": "sing",
            "count": 1
        }
    ]
}

THE END

非常感谢看到最后,作者写的都快要疯掉了,一半的时候就想摆烂放弃了。咬咬牙终于完成了。如果在使用过程中遇到什么问题请一定联系作者,作者一定在第一时间用小本本记录下来。至于改不改。。。日后再说。。

  • TG: DebugST
  • QQ: 2212233137
  • Mail: 2212233137@qq.com
STLib.STJson 简介 路劲疑问 表达式疑问 STJson API 数据类型关系 静态函数 非静态函数 扩展函数 字段 索引器 STJson [基本应用] object -> string string -> object STJson -> object STJsonConverter STJsonAttribute STJsonSetting json_src STJsonPath 选择器 使用方式 通配符 深度选择器 列表选择器 切片选择器 表达式 过滤表达式 普通表达式 测试表达式 内置函数 内置函数列表 自定义函数 表达式中的选择器 选择方式 ParsedTokens STJson [高级应用] 获取值 设置值 回调函数 PathItem Clone 数据聚合 sort group terms min, max avg, sum STJsonPath.Name THE END