2023-06-07 -> by:DebugST

STLib.STJson

Introduction

STLib.STJsonis aJsonparsing library based on theMITopen source protocol. powered by DebugST.The pure native implementation of the library does not rely on any library, so it is very lightweight, convenient, and powerful. Since it does not rely on pure native construction, it can be easily transplanted to other programming languages. Of course, because the author is lazy, the probability is not very high.

When designing STJson, the author did not have any objection. After all, there was no dispute about the data format of Json. When designing STJsonPath, the author hesitated, because the author found many controversial places and could not find a formal RFC document about JsonPath, but only found a draft:

JSONPath: Query expressions for JSON

Although some doubts in the draft can also be solved, the author still has some doubts. After all, it is not an official document. Before development, the author was also curious about other open source libraries of JsonPath, and how other libraries resolve controversial issues. Unfortunately, the problem still exists. No developer seems willing to refer to this draft. Since there is no formal RFC document for JsonPath, the author also wants to establish his own portal. However, the author will not ignore this draft. The author will try to be compatible with the draft as much as possible, and will also add some of his own ideas.

Path question

JsonPath is derived from the use of XPath, which is known to be the XML Path Language, a language used to determine the location of a part of an XML document. Obviously JsonPath is used for Json. They are both used in almost the same way, but the differences between the two data formats XML and Json are bound to have their differences.

For example, there are no special characters in the element name of XML, but the Key of Json can be any string, if there is Json data:

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

In JsonPath we can get the value cc by the path aa.bb without any problem, what if the Json data is replaced by the following:

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

Obviously the value cc is not available through a.a.bb. Maybe normally Json represents an object, and objects don't have such strange properties. But for the data format of Json, its Key can be any character. Even if you pass {OBJ_NAME}.a.a.bb in the browser, you will only get an error. But the way the browser accesses the Json element is not just through . , there is also the indexer {OBJ_NAME}['a.a']['bb'] that can get the correct value.

The use of ' or " is allowed in STJsonPath, which is marked as String in the lexical parser (STJsonPathTokenizer.cs), and if this string is in the scope of a non-expression, the parser (STJsonPathParser.cs) will re-tag it as Property as an index to avoid special strings that can't be handled, and strings support \ for escaping. So you can use 'a.a'.bb in STJsonPath to get the correct value. And a similar treatment is indeed mentioned in the draft.

Expression question

In the draft there is mention of supporting expressions and that there are two types of expressions, () and ? (). They stand for ordinary expressions and filtered expressions respectively. And according to the author's understanding, ordinary expression is used to calculate a value and use this value as part of JsonPath, such as $.books[(@.length - 1)] which can be seen in many cases. It is possible to perform recursion on Json elements when executing JsonPath, where @ is a dynamic variable that indicates the Json element currently being processed during the recursion, and according to the surface meaning of the expression is trying to get the last element in books.

But how does @.length get executed? Where does length come from? If @ is an array, the author can tentatively understand that it is finding the length of the array? But what if @ is an object?

Since it is an expression, what kind of syntax can be written in ()? What about the syntax? About all these queries the author has used his own implementation, which will be introduced in a later tutorial.

STJson API

Data mapping

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

STJson covers the common basic data types, even if they are not included, then the properties of the object will be recurred by performing reflection in the end.

Static functions

(+n) in the following indicates that there are multiple overloads.

returnsignaturenote
stringSerialize(+n)Serialize an object object to a string.
STJsonDeserialize(string)Convert a string to STJson object order.
TDeserialize<T>(+n)Converts an object or string to a target object.
STJsonSTJson CreateObject()Create an empty object。
STJsonSTJson CreateArray(params object[])Create an array object。
STJsonFromObject(+n)Converts an object to STJson.
stringFormat(+n)Format a Json string.
voidAddCustomConverter(+n)Add custom type converter.
voidRemoveCustomConverter(+n)Remove custom type converter

Non-Static functions

returnsignaturenote
STJsonSetItem(+n)Adds a key-value pair to the object and returns itself.
STJsonSetKey(string)Adds a key to the object and returns the target object.
voidSetValue(+n)Set the value of the target object.
STJsonDelete(string)Removes a key from the object and returns the target object.
STJsonAppend(+n)Adds one or some elements to the array object and returns itself.
STJsonInsert(+n)Inserts an element into the array object and returns itself.
STJsonRemoveAt(int nIndex)Removes an index from the array object.
voidClear()Clear all child elements.
IEnumerator<STJson>GetEnumerator()Get all the child elements of the current element.
STJsonCloneClone the current element.

Extended functions

returnsignaturenote
STJsonSet(+n)Set the object according to the path (STJsonPath).
stringGetValue(+n)Get the string value of the object (value-type only)
TGetValue<T>(+n)Get the value of the object (value-type only)
STJsonSelect(+n)Filter data in an object.
STJsonSelectFirst(+n)Filter data in the object and select the first result.
STJsonSelectLast(+n)Filters through the data in the object and selects the last result.
STJsonGroup(+n)Groups the specified fields.
STJsonTerms(+n)Count the number of values for the specified field.
STJsonSort(+n)Sort the specified fields.
STJsonMin(+n)Tally the minimum value of the specified field.
STJsonMax(+n)Tally the maximum value of the specified field.
STJsonSum(+n)Tally the total number of the specified fields.
STJsonAvg(+n)Tally the average of the specified fields.

Fields

typenamenote
stringKeythe Key of the parent element of the current `STJson
objectValuethe value of the current STJson, or null if the current STJson is not of value type
boolIsNullObjectwhether the current STJson is a null element, i.e. the data type of the current STJson value cannot be determined.
intCountThe number of child elements contained in the current STJson.
STJsonValueTypeValueType(enumeration) The data type of the current element.

STJsonValueType is the following value:

Undefined String Boolean Long Double Datetime Array Object

Indexer

STJson [Basic]

STJson is an intermediate data type that is a bridge between string and object and is very convenient to use, e.g:

var st_json = new STJson()
    .SetItem("number", 0)               // The function returns itself, so it can be operated continuously
    .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"
}

When executing var st_json = new STJson(), st_json is the empty element, i.e. st_json.IsNullObject = true. Because at this point it is not possible to determine whether st_json is an object or an array or a value.

There are no objects like JArray or JObject in STJson, STJson can be either Array or Object. STJson has two indexers [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

When ValuteType = Array, SetItem(+n) cannot be called, and when ValueType = Object, Append(+n) cannot be called. But ValueType can be forced to change by SetValue(+n).

As mentioned above STJson has two indexers through which you can access them, or get the values.

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

By the above example maybe you already know how to convert an object to string, by STJson.FromObject(object).ToString(+n), but is it possible that it actually doesn't need to be so troublesome? For example: STJson.Serialize(+n) will do?

In fact STJson.Serialize(+n) would be more efficient because it converts the object directly to a string, rather than converting it to STJson and then to a string.

Console.WriteLine(STJson.Serialize(new { key = "this is test" }));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{"key":"this is test"}

Of course you can have a more friendly output format:

Console.WriteLine(STJson.Serialize(new { key = "this is test" }, 4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "key": "this is test"
}

In fact formatting the output is done by calling the static function STJson.Format(+n). If you don't like the author's built-in formatting style, you can write a formatting function of your own. Or go modify the source file STJson.Statics.cs:Format(string,int).

string -> object

The code doesn't actually convert string to object directly. It has to parse the string to make sure it's a properly formatted Json before it does that. But by the time this is done, you'll already have an STJson object. Finally, STJson is converted to object again.

So you will see the following file in the source code STLib.Json.Converter:

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

There is no StringToObject.cs file inside, and the source code of STJson.Deserialize(+n) is as follows:

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

STJsonConverter

Although there are a lot of data type conversions built into STJson, even data types that are not available are treated as object for recursive processing. But sometimes the situation is not very friendly. For example:

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
}

Obviously, this result is too complicated because all the fields of Rectangle are recursive. But what if this is the case?

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}

where bProcessed is passed in as true by default, and the default processing is used when the upper-level function gets false.

The STJsonConverter provides the Attribute class, which can also be used to tag object attributes.

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

Maybe you don't want to output all the attributes when serializing, then you can control it with 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 is used to add some personalized settings to the serialization or deserialization. Originally the settings were global. But with the increase of some setting items, the author thinks that the global setting is too sticky, so the independent STJsonSetting is used for decoupling. At the same time the independent setting class can also facilitate the subsequent version of the feature expansion, of course the probability of subsequent versions is not very large. Unless the author does not want to be a salty fish.

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

The priority of Attribute is greater than that of STJsonSetting.

json_src

In the next tutorials we will use some test data, which are as follows:

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

To load it into the program:

var json_src = STJson.Deserialize(System.IO.File.ReadAllText("./test.json"));

The appearance of json_src in the subsequent cases is the above object.

STJsonPath

In the source code STJsonExtension.cs the functionality of STJson is extended and some of the functionality of STJsonPath is integrated in it. So there is no dependency on STJsonPath in the original code of STJson, and STJson can be used independently. But STJsonPath, as a helper class of STJson, needs to depend on STJson.

Selector

tokennote
$The root selector, which can be treated as representing the root object.
@current element selector, which refers to the currently traversed element during traversal.
*Wildcard, indicating that it can represent any node.
. <name>Child node selector, specifying the key of the child node.
... depth selector, indicating that it can be any path.
['<name>'(,'<name>')]List selector, specifying the set of keys of the child nodes.
[<number>(,(number))]List selector, specifies the set of index of the child nodes.
[start:end:step]Slice selector specifying the index interval.
[(<expression>)]expression selector for entering an arithmetic expression and continuing down the index with the result.
[? (<expression>)]expression selector, used to enter an arithmetic expression and convert the result to a boolean value to decide whether to continue the selection.

How to use

An STJsonPath can be constructed by:

// var jp = new STJsonPath("$[0]name");
// var jp = new STJsonPath("$[0].name");
var jp = new STJsonPath("[0]'name'"); // All of the above methods can be used, $ is not must.
Console.WriteLine(jp.Select(json_src));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
["Tom"]

Of course STJsonPath is already integrated in the extension functions in STJson and can be used directly by the following:

// var jp = new STJsonPath("[0].name");
// Console.WriteLine(json_src.Select(jp));
Console.WriteLine(json_src.Select("[0].name")); // Internal dynamic construction of STJsonPath
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
["Tom"]

STJsonPath returns data as an array, and its return value is STJson instead of List<STJson>, STJson can also be an array object.

The beginning of $ is not required for STJsonPath and the beginning $ or @ is removed internally, $@ is only used as a variable of the object in the expression.

The return value of a selector in an expression returns only the first result selected. This is not an array list, as will be explained later.

The use of ' or " is allowed in STJsonPath, for example: 'a.b' "a.b" STJsonPath will treat it as a separate entity. Instead of two. Listing such as Json as follows:

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

It is obvious that the data cannot be obtained by Select("a.b"), you need to pass 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"]

Support for \ in strings for escaping:\r\n\t\f\b\a\v\0\x..\u...\.

Wildcard character

Wildcards can represent any node of the current level. Gets the names of all people.

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

Depth selector

Depth selectors are similar to wildcards, but depth selectors can be at any level.

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

List selector

List selectors support both int and string. Although two list selectors are listed in the table of selectors above, there is only one list selector in STJsonPath, and they can be mixed, for example, the following uses are both legal:

STJsonPath will automatically split into two list selectors internally and determine which list selector to use by judging STJsonValueType. The internal implementation code is as follows:

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;

Selects the elements with index 0 and 2.

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

Negative numbers can be used for int indexes, e.g. -1 means get the last element. When STJsonPath detects a negative number it will execute STJson.Count - n to use the result as index.

//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"
        ]
    }
]

Slice selector

The slice selector is used to select a fragment in an array. The default value of the slice selector is [0:-1:1], which is implemented inside the slice selector as follows:

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;

So the three values in the slice are equivalent to the three conditions in the for loop, so the principle and effect will not be explained.

expressionrangenote
[::]0 <= R <= (OBJ).length - 1equivalent to *.
[5:]5 <= R <= {OBJ}.length - 1Gets all elements starting with the 6th element.
[-1:0]{OBJ}.length - 1 >= R >= 0Get the data in reverse order.
[0::2]0 <= R <= {OBJ}.length - 1Fetch data in order and one data interval apart.

At least one : appears in the slice selector and step is greater than 0, otherwise it will get an exception.

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

Expression

The following operators can be supported in [?()], with increasing priority from top to bottom.


operatornotee.g
reRegular expressions. [?(@.name re 'un')]
inThe left value or array is contained in the right array. [?(@.age in [16,20])]
ninThe value or array on the left is not contained in the array on the right. [?(@.hobby nin ['sing','draw'])]
anyofThe intersection of the value or array on the left and the array on the right exists. [?(@.hobby anyof ['sing','draw'])]

Expressions have two modes:

Filter expression

Check the elements of name that contain the letters 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"
        ]
    }
]

The i in (?i) means case is ignored, and its regular expression is based on the Regex in . (?...) starts with (?...)` to set the matching pattern. As for the matching pattern, please check the related information by yourself.

Check the elements of hobby that do not contain sing and swing:

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

General expression

The general expression will continue matching the result as part of STJsonPath.

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

In [('na' + 'me')] the result of 'na' + 'me' is 'name' and will use this value as an index, so the above effect is equivalent to *.name, but of course the return value can also be a collection.

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

The result of the above expression settles on the value ['name', 'age', 0, 2]. But obviously 0 and 2 will not do anything, because the second level data object is not an array.

The above expression is equivalent to *. ['name', 'age', 0, 2]. If you replace the above with the third level you will get the following result.

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

You can see that 'name' and 'age' are not valid for hobby because hobby is an array.

Test expression

The author provides a static test function TestExpression() that can be used to debug the expression. If there is something you don't understand, test it and you will see the process and the result.

Console.WriteLine(STJsonPath.TestExpression(
    null,           // [STJson] is used to replace the $ that appears in the expression.
    null,           // [STJson] is used to replace the @ that appears in the expression.
    "1+2+3"         // Expression text.
    ).ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "type": "expression",
    "parsed": "{1 + 2 + 3}",        // The formatted text {} means that this part needs to be executed separately e.g. [1, {1+1}, 3].
    "polish": [
        "1", "2", " + ", "3", " + " // Inverse Polish expressions.
    ],
    "steps": [                      // The excute steps
        {
            "type": "excute",
            "operator": "+",
            "get_left_token": {     // Computes the value of the element to the left of the operator, the left side of the expression may also be an expression.
                "parsed": "1",
                "type": "value",
                "result": {
                    "value_type": "Long",
                    "text": "1"
                }
            },
            "get_right_token": {
                "parsed": "2",
                "type": "value",
                "result": {
                    "value_type": "Long",
                    "text": "2"
                }
            },
            "result": {             // The result of this step execution.
                "value_type": "Long",
                "text": "3"
            }
        }, {
            "type": "excute",
            "operator": "+",
            "get_left_token": {     // In this case, the element to the left of the operator is the result of the calculation of the previous step.
                "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": {               // Clears the Polish expression data stack and determines the final output.
        "parsed": "6",
        "type": "value",
        "result": {
            "value_type": "Long",
            "text": "6"
        }
    },
    "return": {                     // The final return value.
        "value_type": "Long",
        "text": "6",
        "bool": true                // Converts to true if used as a Boolean expression.
    }
}

If the process is not important and you simply want to see the results of the execution.

Console.WriteLine(STJsonPath.TestExpression(
    null,           // [STJson] is used to replace the $ that appears in the expression.
    null,           // [STJson] is used to replace the @ that appears in the expression.
    "1+2+3"         // Expression text.
    ).SelectFirst("return").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "return": {                     // The final return value.
        "value_type": "Long",
        "text": "6",
        "bool": true                // // Converts to true if used as a Boolean expression.
    }
}

Build-In functions

returnsignaturenote
stringtypeof(+n)Get the data type.
stringstr(+n)Convert to string.
stringupper(+n)Convert to uppercase.
stringlower(+n)Convert to lowercase.
longlen(+n)Get the length of a string or an array.
longlong(+n)Convert to integer.
doubledouble(+n)Convert to double.
long or doubleabs(+n)Get the absolute value.
longround(+n)Round up to round down.
longceil(+n)Round up.

typeof has the following return values:

string long double boolean array object undefined

The above functions can be signed with the following signature:

where bool indicates whether the result is expected as an array, if true then the result is output as an array, and when object is an array, the data in it is recursive.

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

Different functions use different default values for the second parameter. The following are some other functions that the reader can test the effect themselves by TestExpression().

returnsignaturenote
long or doublemax(+1)Get the maximum value.
long or doublemin(+1)Get the minimum value.
long or doubleavg(+1)Get the average.
long or doublesum(+1)Get the summary.
stringtrim(+n)Crop the specified characters at both ends of the string.
stringtrims(+n)Cuts the character at the beginning of the string.
stringtrime(+n)Cuts the character at the end of the string.
string_arraysplit(+1)split the string.
long or stringtime(+n)Get or format the timestamp.

Build-In functions list

As the version is iteratively updated (if possible), the built-in functions may change at any time. The GetBuildInFunctionList() allows you to view information about the built-in functions supported by the current version.

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

The reader can test the function by following demos with TestExpression().

Select elements whose hobby length is greater than 2:

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

Custom functions

When providing built-in functions for expressions, the authors didn't know what kind of functions the developers expected, so they built a few and then just left them alone. There is no problem that can't be solved by writing code, yes. Let the developers write their own. But how do developers provide functions for STJsonPath?

STJsonPath.CustomFunctions is a static dictionary. It is used to hold developer-defined functions. Its function signatures are as follows:

public delegate STJson STJsonPathCustomFuncHander(STJsonPathExpFuncArg[] args);

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

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

The return value of the custom function is unified with STJson, because STJson can interface perfectly with STJsonPath, where STJsonPath can operate directly on the return value of the custom function using selector. For example, the following example adds a matches function to STJsonPath for regular expression manipulation.

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;
});

Then we need to filter out the elements of hobby that contain two as or os:

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

The custom function takes precedence over the built-in function, that is, if there is a function with the same name in the custom function and the built-in function, the custom function is called first.

Selectors in expressions

A selector in an expression returns only the first result selected, not a list of arrays.

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": {                     // The return value will get only the first result.
            "value_type": "String",
            "text": "Tom"
        }
    },
    "return": {
        "bool": true,
        "value_type": "String",
        "text": "Tom"
    }
}

Select mode

Normal mode (default):

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

Path mode:

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

Structure mode:

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

ParsedTokens

GetParsedTokens() is used to get how the current STJsonPath string is parsed internally and output as STJson. If you want to write a parser too, it might give you some ideas.

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 [Advanced]

Get value

Imagine a scenario where, as a WEB back-end service, we need to process Json data submitted by the front-end, assuming we expect the following Json data:

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

And we have converted the above data into STJson and named it json_post. Where from and size are used for page-flipping function. Then the backend may make the following judgments:

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>();
// Or just be rude.
int nFrom = 0;
try{
    nFrom = json_post["page"]["from"].GetValue<int>();
}catch{
    /* do something */
}

Obviously the above code is driving you crazy. Of course, you can create an entity object for it in the backend code to reduce the hassle, and then bind Json to the entity object. But creating an entity object for each Post data type is also quite annoying. But if you use the following code.

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

Maybe you are wondering, what if the path does not exist or from is not a number at all?emm.... The above code will still report an error, because the internal call to the above code is:

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;
}

Because when there is no element, do not know what value needs to be returned, the reader may think that it is impossible to get the value, return a default value is good, indeed, can be so designed, but the caller how to determine the value returned is the real value or the default value. There is no way to determine if it is an exception. Unless a default value is forced to be specified.

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

Returns the element value if the element exists and can be converted properly, otherwise returns 0.

Or to know if the real value is returned:

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

If the filled strJsonPath will get more than one value, the first value is taken.

Set value

If we need to construct a Json manually we can add elements via STJson.SetItem(). However, some scenarios seem a bit tricky to use this way, for example, take the query syntax of the ElasticSearch database as an example. We need to construct a data as follows:

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

Then we need to construct an STJson object as in the previous case like this:

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"
        }
    }
});

But it is also possible to write the code like this:

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

Or even this:

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

Only list selector and slice selector are supported in Set. No other selectors are supported.

Callback

With the Set above are we able to add a coding to each of the hobbies in json_src? It doesn't seem possible. After all, adding data to hobby requires append not set.

Of course there is not no way around it.

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"
        ]
    },
    ...
]

Two kinds of callback functions are supported in Select, one requires a return value and the other does not, as in the above. And the following one with return value.

var json = json_src.Select("*.hobby.*", (p, j) => {
    return new STJsonPathCallBackResult() {
        Selected = true,            // If or not to add this json to the result.
        Json = new STJson()         // The json that needs to be added to the result.
            .SetItem("path", p)
            .SetItem("item", j.Value.ToString().ToUpper())
    };
});
Console.WriteLine(json.ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
[
    {
        "path": [
            0, "hobby", 0
        ],
        "item": "COOKING"
    },
    ...
]

PathItem

There is a rather special data structure in STJsonPath.

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

Its structure can be restored by 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

If you want to add coding to all user hobbies. but don't want to affect the source data, then you can clone a copy of the data for the operation.

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"
        ]
    },
    ...
]

Aggregation

There are some extensions built into STJson for aggregate operations and simple data processing. Combined with STJsonPath, you can do some data manipulation quickly and easily.

sort

Sort() is used to sort the data, internally in a Merge Sort manner. This function has the following formal signature:

returnsignaturenote
STJsonSort()Sort without specifying path. Default is ascending.
STJsonSort(bool)Sort without specifying path, specifying whether to sort in descending order.
STJsonSort(params object[])Sort without specifying path or whether to sort in descending order.

Sorting without specifying 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]

Are you wondering why the result is the way it is? But before that why don't you wonder why you need to use such an array to sort?

Inside Sort, a number is obtained for each element and the index of the element is recorded, then the number is sorted and finally an array is reassembled according to the index position to achieve the sorting purpose. The rules for how to get the number of an element are as follows:

switch (item.ValueType) {
    case STJsonValueType.Long:
    case STJsonValueType.Double:
        d_temp = item.GetValue<double>();
        break;
    case STJsonValueType.Boolean: // true will convert to 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:    // For other types just let them always be at the end.
        d_temp = arr_b_desc[j] ? double.MinValue : double.MaxValue;
        break;
}

So if you don't have that weird array then everything will be fine. What if I really want to sort only the numbers inside?

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]

Specify path for sorting:

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

Of course you can also specify a descending order:

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

Multiple fields can also be specified for sorting at the same time:

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

The above is to use gender for ascending order, and then for the same value and then age for descending order. The Sort function parameter rules are as follows:

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); // internal
}

group

Group() is used to group the data by specifying path. It is used in the following way:

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

Similarly you can use multiple paths for grouping, but unlike Sort these paths are not grouping the results of the first path. They are juxtaposed, not nested.

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

What should I do if I want to do nested Group()? The author does not intend to implement it in Group(), because STJsonPath can do the job.

terms

Terms() is similar to aggregation in the ElasticSearch database, counting the number of occurrences of a field, and like Sort(), it can choose whether to specify path or multiple paths.

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
        }
    ]
}

As you can see, it seems that hobby doesn't sort count? Yes, it does. This is intentional on the author's part.... What? Not convinced? Is it possible that there is actually a role for Terms()? There is a cardinality in the ES database. It is used to count the number of data in a field after it has been de-duplicated.

Is it possible that the author actually wanted to implement a function like this? But halfway through writing this function, the author Deleted it on the spot. There's no need for that. Isn't the Terms() above already doing the job? For example:

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

And is there a possibility that cardinality only counts the number of individuals. But.... What if you want to get the values of the de-duplicated fields? And it seems that Terms() has already been done in all these cases. It's just not sorting. We already have Sort() function, and then a separate order is difficult?

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

What do I have to do if I want to get the top three most popular hobbies for personnel?

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() is to find the minimum and maximum elements, respectively, and can be specified without or with multiple paths.

Console.WriteLine(json_src.Min("age").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "age": {
        "count": 4,                 // Four of these elements are involved in the calculation.
        "value": 16,                // The minimum age value is 16.
        "items": [                  // The element that satisfies the minimum value.
            {
                "name": "Tom",
                "age": 16,
                "gender": 0,
                "hobby": [
                    "cooking", "sing"
                ]
            }, {
                "name": "Tony",
                "age": 16,
                "gender": 0,
                "hobby": [
                    "game", "dance"
                ]
            }
        ]
    }
}

In fact, the effect above is a bit like Group a bit like that, plus a Sort(). It's almost exactly the same. Even the author himself is confused. We should have used Group() + Sort() inside Min/Max() to achieve this. Forget about it. The code is written. But it is clear that Sort() will certainly reduce efficiency. But then again. The small amount of data does not care about efficiency. But what about big data? Big data volume you give me that you use Json array to save ???? Are you sick?

avg, sum

Avg() Sum() is used to calculate the average and the summary, respectively, and can be unspecified or specify multiple keys.

Console.WriteLine(json_src.Avg("age").ToString(4));
/*******************************************************************************
 *                                [output]                                     *
 *******************************************************************************/
{
    "age": {
        "count": 4,         // Four of these elements are involved in the calculation.
        "value": 19.5       // avg is 19.5
    }
}

The return value of Sum() is the same as Avg(), the only difference is that value is an average and a summary.

STJsonPath.Name

In the above demo, we have specified a field for data manipulation, and the resulting json will output the field name as a key. But the above does not emphasize that the operation is performed on the specified key, but on the path. It's just that the path is simpler to make it look like key to us.

What if aggregation is done this way?

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
        }
    ]
}

It seems like it looks strange. Actually STJsonPath has a Name property.

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

Finally, thank you so damn much for seeing it through to the end, the author is about to go crazy writing it. If you encounter any problems during the use, please inform the author in time if you can. The author will certainly be very promptly recorded, as to change or not ...... We'll talk about it later.

  • TG: DebugST
  • QQ: 2212233137
  • Mail: 2212233137@qq.com
STLib.STJson Introduction Path question Expression question STJson API Data mapping Static functions Non-Static functions Extended functions Fields Indexer STJson [Basic] object -> string string -> object STJsonConverter STJsonAttribute STJsonSetting json_src STJsonPath Selector How to use Wildcard character Depth selector List selector Slice selector Expression Filter expression General expression Test expression Build-In functions Build-In functions list Custom functions Selectors in expressions Select mode ParsedTokens STJson [Advanced] Get value Set value Callback PathItem Clone Aggregation sort group terms min, max avg, sum STJsonPath.Name The END