STLib.Json is a Json parsing library based on the MIT open source protocol . Developed by DebugST, the library is implemented natively without any library dependencies , so it is very lightweight , convenient and powerful . Because there is no use of any dependencies built natively, it can be easily ported to other programming languages. Of course, due to the author is very lazy, this probability is not very big, just dream about it, do not take it too seriously.
So why develop STJson? As we all know, .NET comes with a parsing library that is too user-friendly, and I'm sure you're not used to using it. And it just so happens that the author always needs to use JSON data processing at the moment. Although there are some third-party libraries that can be used, after the author's experience, he thought, forget about doing it himself. There are two main points.
In the design of STJson the author does not have any objection, after all, Json data format does not exist controversial place, of course, in the compatibility of JSON5 the author still add part of their own ideas, anyway, the current JSON5 is not the official document.
When designing STJsonPath, the authors hesitated because they found a lot of controversy and were unable to find an official RFC document for JsonPath, only a draft:
JSONPath: Query expressions for JSON
Although some of the 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 JsonPath open source libraries and how other libraries solved the controversial areas. Unfortunately, the problem persists. No library seems to be willing to be compatible with this draft. Since there is no official RFC documentation for JsonPath, the authors will try to be as compatible as possible with the draft while adding some of their own ideas.
JsonPath is derived from the use of XPath, which is known as the XML Path Language, a language used to determine the location of a part of an XML document. Obviously JsonPath is used for Json. Both of them are used in almost the same way, but the differences in the XML and Json data formats are bound to have 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 a bit of Json data:
{ "aa": { "bb": "cc" } }
In JsonPath we can get the value cc by the path aa.bb and there is no problem, what if the Json data is replaced with the following:
{ "a.a": { "bb": "cc" } }
Obviously the value cc is not available through a.a.bb. Perhaps normally Json represents some object, and objects don't have such strange properties. But for the Json data format, its Key can be any character. Even if you pass {OBJ_NAME}.a.a.bb in the browser, you'll just get an error. But browsers don't access Json elements only by . , but also the indexer {OBJ_NAME}['a.a']['bb'] to get the correct value.
The use of ' or " is allowed in STJsonPath, which will be 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 to be used as an index, thus avoiding cases where special strings can't be handled, and strings support \ for escaping. So you can use 'a.a'.bb in STJsonPath to get the correct value. And similar handling is indeed mentioned in the draft.
In the draft there is a reference to support for expressions and that there are two types of expressions, () and ? (). These represent regular expressions and filter expressions respectively. According to the author's understanding, regular expression is used to calculate a value and use this value as part of JsonPath, such as $.books[(@.length - 1)] in many cases. When executing JsonPath it is possible to perform a cascading recursion on the Json elements, where @ is a dynamic variable indicating the Json element currently being processed during the recursion, and according to the expression's apparent meaning it is trying to get the penultimate element in books.
But how does @.length get executed? Where does length come from? If @ is an array, the author can tentatively understand that he is asking for 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 grammar? Regarding all these queries the author has used his own implementation, which will be presented in a later tutorial.
.Net | STJsonValueType | .Net | STJsonValueType |
---|---|---|---|
byte | Long | sbyte | Long |
short | Long | ushort | Long |
int | Long | uint | Long |
long | Long | ulong | Long |
float | Double | double | Double |
decimal | Double | bool | Boolean |
char | String | string | String |
DateTime | String | enum | Long or String |
Point | Array | PointF | Array |
Size | Array | SizeF | Array |
Rectangle | Array | RectangleF | Array |
Color | Array | DataTable | Object |
Array | Array | ICollection | Array |
IDectionary | Object | object | Object |
STJson covers common basic data types, and even if they are not included, then the properties of the object will eventually be recursed by performing reflection.
In the following, (+n) means that there is more than one overloading.
return | signature | note |
---|---|---|
string | Serialize(+n) | Serializes an object object to a string. |
STJson | Deserialize(string) | Convert a string to an STJson object. |
T | Deserialize<T>(+n) | Converts an object or string to a target object. |
STJsonWriter | Write | Create an STJsonWriter object. |
STJsonReader | Read | Create an STJsonReader object. |
STJson | Create(STJsonCreator) | Create an STJson object. |
STJson | CreateObject() | Creates an Null object. |
STJson | CreateArray(params object[]) | Creates an array object.。 |
STJson | FromObject(+n) | Convert an object to STJson. |
string | Format(+n) | Formats a Json string. |
void | AddCustomConverter(+n) | Custom type converters. |
void | RemoveCustomConverter(+n) | Remove the custom converter. |
return | signature | note |
---|---|---|
STJson | SetItem(+n) | Add a key-value pair to the object and returns itself. |
STJson | SetKey(string) | Add a key to the object and returns the target object. |
void | GetValue(+n) | Get the value of the target object. |
void | SetValue(+n) | Set the value of the target object. |
STJson | Remove(string) | Remove a key from an object and returns the target object. |
STJson | Append(+n) | Add an element or elements to an array object and returns itself. |
STJson | Insert(+n) | Insert an element or elements to an array object and returns itself. |
STJson | RemoveAt(int nIndex) | Remove an index from an array object. |
void | Clear() | Clear all elements. |
IEnumerator<STJson> | GetEnumerator() | Get all elements. |
STJson | Clone | Clone current STJson。 |
return | signature | note |
---|---|---|
bool | IsNullOrNullValue | Check current STJson is null or the Undefined. |
STJson | Set(+n) | Set the object according to the path (STJsonPath). |
string | GetValue(+n) | Get the string value of the object (value type only). |
T | GetValue<T>(+n) | Get value from value(value type only). |
STJson | Select(+n) | Filter the data in the object. |
STJson | SelectFirst(+n) | Filter the data in the object and check the first result. |
STJson | SelectLast(+n) | Filter the data in the object and check the last result. |
STJson | Group(+n) | Groups the specified fields. |
STJson | Terms(+n) | Counts the number of values in the specified field. |
STJson | Sort(+n) | Sort the array. |
STJson | Min(+n) | Get the minimum value from the array. |
STJson | Max(+n) | Get the maximum value from the array. |
STJson | Sum(+n) | Get the summary from the array. |
STJson | Avg(+n) | Get the average value from the array. |
type | name | note |
---|---|---|
string | Key | The Key of the current STJson parent element. |
object | Value | The value of the current STJson, or null if the current STJson is not a value type. |
bool | IsNullValue | Whether the current STJson is an empty element, i.e. it is not possible to determine the data type of the current STJson value. |
int | Count | The number of child elements contained in the current STJson. |
STJsonValueType | ValueType | (Enumeration) The data type of the current element. |
STJsonValueType is the following value:
Undefined String Boolean Long Double Datetime Array Object
name | note |
---|---|
STJsonCreator | Used to create a complex STJson object. |
STJsonReader | Used to dynamically parse objects from a TextReader. Typically used to read a Json with larger data from a file. |
STJsonWriter | Used to write a Json string to a TextWriter. Typically used to write a large and complex Json data directly to a file. |
STJson is an intermediate data type that bridges between string and object and is very convenient to use, for example:
var st_json = new STJson() .SetItem("number", 0) // The function returns itself, so it can operate 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.IsNullValue = true. Because at this point it is not possible to determine whether st_json is an object or an array or a value.
There is no object 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.IsNullValue + " - " + json_1.ValueType); var json_2 = new STJson(); json_2.SetItem("key", "value"); Console.WriteLine("[json_2] - " + json_2.IsNullValue + " - " + json_2.ValueType); var json_3 = new STJson(); json_3.Append(1, 2, 3); Console.WriteLine("[json_3] - " + json_3.IsNullValue + " - " + json_3.ValueType); var json_4 = new STJson(); json_4.SetValue(DateTime.Now); Console.WriteLine("[json_4] - " + json_4.IsNullValue + " - " + json_4.ValueType); var json_5 = STJson.CreateArray(); // made by static function Console.WriteLine("[json_5] - " + json_5.IsNullValue + " - " + json_5.ValueType); var json_6 = STJson.CreateObject(); // made by static function Console.WriteLine("[json_6] - " + json_6.IsNullValue + " - " + json_6.ValueType); var json_7 = STJson.FromObject(12); // made by static function Console.WriteLine("[json_3] - " + json_7.IsNullValue + " - " + json_7.ValueType); /******************************************************************************* * [output] * *******************************************************************************/ [json_1] - True - Undefined [json_2] - False - Object [json_3] - False - Array [json_4] - False - Datetime [json_5] - False - Array [json_6] - False - Object [json_7] - False - Long
Normally SetItem(+n) sets the ValueType to Object, and Append(+n) Insert(+n) sets the ValueType to Array.
As mentioned above STJson has two indexers that can be used to access them, or get 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
With 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 doesn't really have to be so tedious? For example, STJson.Serialize(+n) would be fine????
Actually STJson.Serialize(+n) would be more efficient, because it converts the object directly to a string, instead of converting 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" }
Or you can serialize directly into a TextWriter:
STJson.Serialize(new { key = "this is test" }, new StreamWriter("./test.json")));
In fact the code does not directly convert string to object. That's because the string has to be parsed before that to make sure it's a properly formatted Json. But by the time it's done, it's already got an STJson object. Finally the STJson is converted to object.
So you will see the following file in the source code STLib.Json.Converter:
ObjectToSTJson.cs ObjectToString.cs STJsonToObject.cs
There is no StringToObject.cs file in there, and the source code for STJson.Deserialize(+n) is as follows:
public static T Deserialize<T>(string strJson, +n) { var json = STJsonParser.Parse(strJson); return STJsonToObject.Get<T>(json, +n); }
I'm sure the author doesn't need to explain how to convert a string to an object and the reader should know how to handle it, but it's worth stating here that STJson can be appended to an object to enable localized updates.
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}
Although there are a lot of type conversions built into STJson, and even types that aren't there are recursive. 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 overly complicated because all the fields of Rectangle are recursively out. 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 with a default value of true, and the default processing is used when the higher level function gets false.
STJsonConverter provides the Attribute class, which can also be used to mark object attributes.
public class Test{ [STJsonConverter(typeof(RectangleConverter))] public Rectangle Rect{get; set;} }
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; } }
Maybe you don't want to output all the attributes when serializing, then you can control that 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 is used to add some personalized settings in serialization or deserialization. Originally, the settings were global. But with the increase of some settings, the author thinks the global settings are too coupled, so STJsonSetting is independent to decouple them. And the independent settings class can also facilitate the expansion of functionality in subsequent versions, of course, the probability of subsequent versions is not very large. Because the author is very lazy.
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 has a higher priority than STJsonSetting.
Support for JSON5 is provided in STJson(3.0), and the author provides some more convenient ideas, as exemplified by the following text:
json5.txt{
"normal_key": "This is a normal key and value.", // this is a line comment。
/*
this is a block comment。
*/
str_value_1: 'string_1', // For a key the ["] is not must.
str_value_2: "string_2", // For a string you can use ['] or ["]
int_numbers: [ // Hex number
0x123, -0x123, +123, -123
],
'float_numbers':[
.123, 123., -.123, +123., +123E-2
],
// From json 5 -> https://json5.org
string_1: "string_1.\
string_2.\
|<- some space",
// But in STJson you can just use [\r\n] line breaks and keep the line breaks.
string_2: "string_1.
string_2.
|<- some space",
// Or even use it that way. The authors consider JSON5's line continuation to be unfriendly.
// After a line break via [\], the new line cannot be preceded by whitespace or it will be parsed into the string.
// So STJson allows consecutive strings and eventually merges them into a single string.
string_3:
"string_1."
"string_2."
"\r\nstring_3.",
array:[
123,true,"string_1.""string_2.","string_3",
],
}
var str_file = "./json5.txt"; var str_json = File.ReadAllText(str_file, Encoding.UTF8); var json = STJson.Deserialize(str_json); Console.WriteLine(json.ToString(4)); /******************************************************************************* * [output] * *******************************************************************************/ { "normal_key": "This is a normal key and value.", "str_value_1": "string_1", "str_value_2": "string_2", "int_numbers": [ 291, -291, 123, -123 ], "float_numbers": [ 0.123, 123, -0.123, 123, 1.23 ], "string_1": "string_1.string_2. |<- some space", "string_2": "string_1.\r\nstring_2.\r\n |<- some space", "string_3": "string_1.string_2.\r\nstring_3.", "array": [ 123, true, "string_1.string_2.", "string_3" ] }
STJsonReader can take characters from a TextReader and parse them dynamically, usually used for parsing large text data. E.g. load a Json data from a file or stream via StreamReader.
string str_json = @" { name: 'DebugST', language: ['C#', 'JS'], address: { country: 'China', province: 'GuangDong', city: 'ShenZhen' } }"; using (var reader = new STJsonReader(new StringReader(str_json))) { foreach (var v in reader) { //Console.WriteLine(STJson.Serialize(v, 4)); Console.WriteLine(v.Path + ": " + v.Text + " - [" + v.ValueType + "]"); } } /******************************************************************************* * [output] * *******************************************************************************/ name: DebugST - [String] language: [...] - [Array] language[0]: C# - [String] language[1]: JS - [String] address: {...} - [Object] address.country: China - [String] address.province: GuangDong - [String] address.city: ShenZhen - [String]
By default STJsonReader recurses all data. Change the code slightly:
foreach (var v in STJson.Read(new StringReader(str_json))) { if (v.Path == "language") { Console.WriteLine(v.GetSTJson().ToString(4)); } else { Console.WriteLine(v.Path + ": " + v.Text + " - [" + v.ValueType + "]"); } } /******************************************************************************* * [output] * *******************************************************************************/ name: DebugST - [String] [ "C#", "JS" ] address: {...} - [Object] address.country: China - [String] address.province: GuangDong - [String] address.city: ShenZhen - [String]
GetSTJson() will keep fetching data backwards from the current position until it stops fetching a complete STJson object. And as you can see, in subsequent iterations, language has been skipped because he was fetched by GetSTJson().
STJsonReader performance is much lower than STJson.Deserialize(+n), and there is no intention to optimize STJsonReader at this time. The authors believe that STJsonReader is only used in special scenarios, however special scenarios are not considered for performance.
And since STJsonReader uses dynamic parsing, and STJson is not parsed by a state machine, the author does not intend to do strict Json data format checking. So the following code also works fine and is equivalent to str_json above.
string str_json = @" { name 'DebugST' language: ['C#' 'JS'], address: { country: 'China',,,,,,, province: 'GuangDong',,,, city:::::::::::: 'ShenZhen'";
string str_json = @" { name: 'DebugST', language: ['C#', 'JS'], address: { country: 'China',,,,,,, province: 'GuangDong',,,, city:::::::::::: 'ShenZhen' }}}}}}}}}} }1234567890";
When STJsonReader is dynamically parsed, the author ignores :,. This way, when reading a key-value pair, you only need to read two tokens. The first one is used as the key and the second one is used as the value. Part of the source code is as follows:
private STJsonToken GetNextFilteredToken() { foreach (var v in m_token_reader) { switch (v.Type) { case STJsonTokenType.KVSplitor: // : case STJsonTokenType.ItemSplitor: // , continue; default: return v; } } return STJsonToken.None; } private STJsonReaderItem GetNextObjectKV() { var token_key = this.GetNextFilteredToken(); switch (token_key.Type) { case STJsonTokenType.None: return null; case STJsonTokenType.ObjectEnd: this.PopStack(); return this.GetNextItem(); case STJsonTokenType.Symbol: case STJsonTokenType.String: break; default: throw new STJsonParseException(token_key); } m_current_stack.Key = token_key.Value; var token_val = this.GetNextFilteredToken(); if (token_val.Type == STJsonTokenType.None) { return null; //throw new Exception("error"); } var item = new STJsonReaderItem(this, token_val) { ParentType = STJsonValueType.Object, Key = token_key.Value, Text = token_val.Value }; return this.CheckValueToken(item, token_val); } private STJsonReaderItem GetNextArrayItem(){...}
STJsonWriter plays the opposite role to STJsonReader, STJsonWriter can be used to construct and write Json strings in real time directly to a TextWriter or stream.
StringWriter sw = new StringWriter(); //using (var writer = new STJsonWriter(sw)) { // writer.StartWithArray((w) => // { // // writer == w // }); //} STJson.Write(sw, 4).StartWithArray((w) => { for (int i = 0; i < 1; i++) { Console.WriteLine("Level:" + w.Level); w.CreateObject(() => { Console.WriteLine("Level:" + w.Level); w .SetItem("name", "DebugST") //.SetItem("language", STJson.CreateArray("C#", "JS")) //.SetItem("language", new string[] { "C#", "JS" }) .SetArray("language", () => { Console.WriteLine("Level:" + w.Level); w .Append("C#") .Append("JS"); }) .SetObject("address", () => { Console.WriteLine("Level:" + w.Level); w .SetItem("country", "china") .SetItem("province", "GuangDong") .SetItem("city", "ShenZhen"); }); }); } }); Console.WriteLine(sw.ToString()); /******************************************************************************* * [output] * *******************************************************************************/ Level:1 Level:2 Level:3 Level:3 [ { "name": "DebugST", "language": [ "C#", "JS" ], "address": { "country": "china", "province": "GuangDong", "city": "ShenZhen" } } ]
STJsonWriter uses a hierarchical approach to callbacks in order to be consistent with the data structure hierarchy of the target Json. As shown above, STJsonWriter manages the level of callback functions internally.
In the next tutorial we will use some test data, which is 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"
]
}]
Load it into the program:
var json_src = STJson.Deserialize(System.IO.File.ReadAllText("./test.json"));
Subsequent cases where json_src appears are the above objects.
In the source code STJsonExtension.cs, the functionality of STJson is extended, which integrates some functionality of STJsonPath. So there is no dependency on STJsonPath in the original STJson code, STJson can be used independently. But STJsonPath is a helper class of STJson, it needs to depend on STJson.
token | note |
---|---|
$ | 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. |
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....\.
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 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 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" ] } ]
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.
expression | range | note |
---|---|---|
[::] | 0 <= R <= (OBJ).length - 1 | equivalent to *. |
[5:] | 5 <= R <= {OBJ}.length - 1 | Gets all elements starting with the 6th element. |
[-1:0] | {OBJ}.length - 1 >= R >= 0 | Get the data in reverse order. |
[0::2] | 0 <= R <= {OBJ}.length - 1 | Fetch 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" ] } ]
The following operators can be supported in [?()], with increasing priority from top to bottom.
operator | note | e.g |
---|---|---|
re | Regular expressions. | [?(@.name re 'un')] |
in | The left value or array is contained in the right array. | [?(@.age in [16,20])] |
nin | The value or array on the left is not contained in the array on the right. | [?(@.hobby nin ['sing','draw'])] |
anyof | The intersection of the value or array on the left and the array on the right exists. | [?(@.hobby anyof ['sing','draw'])] |
Expressions have two modes:
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" ] } ]
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.
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. } }
return | signature | note |
---|---|---|
string | typeof(object) | Get the data type. |
string | str(object) | Convert to string. |
string | upper(string) | Convert to uppercase. |
string | lower(string) | Convert to lowercase. |
long | len(string or array) | Get the length of a string or an array. |
long | long(string or number) | Convert to integer. |
double | double(string or number) | Convert to double. |
number | abs(number) | Get the absolute value. |
long | round(number) | Round up to round down. |
long | ceil(number) | Round up. |
long or double | max(array) | Get the maximum value. |
long or double | min(array) | Get the minimum value. |
long or double | avg(array) | Get the average. |
long or double | sum(array) | Get the summary. |
string | trim(string) | Crop the specified characters at both ends of the string. |
string | trims(string) | Cuts the character at the beginning of the string. |
string | trime(string) | Cuts the character at the end of the string. |
string_array | split(+1) | split the string. |
long or string | time(+n) | Get or format the timestamp. |
typeof has the following return values:
string long double boolean array object undefined
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')" ] }, ... ]
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" ] } ]
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.
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" } }
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" } ]
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": "==" } ] } ] }
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.
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.
Although it is easy to construct a Json data by Set above, it is also possible to do it by Set for multi-layer nested data structure, but the code will become too complicated and it is not possible to visualize the structure of Json data. STJsonCreator is similar to STJsonWriter, but STJsonCreator creates an STJson object directly, which is usually used to create complex Json objects. Still using the ElasticSearch database as an example to simulate a data retrieval.
var str_json_post = @" { type: 'student', names: ['DebugST', 'None'], }"; // The web post data. var json_post = STJson.Deserialize(str_json_post); var json_es_query = STJson.Create((c) => // Create ES query json. { c .SetItem("from", (json_post.GetValue("page", 1) - 1) * json_post.GetValue("size", 10)) .SetItem("size", json_post.GetValue("size", 10)) .Set("query.bool", () => { c .Set("filter", () => { c.Set("term.type", json_post["type"]); }) // c.Set(str_path, bool, callback) bool -> if true, create this path. .Set("should", !json_post["names"].IsNullOrNullValue(), () => { // c.Append(0, 5, 1, (i)=>{ }) => for(int i = 0; i < 5; i+=1) {...} // c.Append(IEnumerable, callback); c.Append(json_post["names"], (item) => // => foreach(var item in json_post) {...} { c.Set("term.name", item); }); }); }); }); Console.WriteLine(json_es_query.ToString(4)); /******************************************************************************* * [output] * *******************************************************************************/ { "from": 0, "size": 10, "query": { "bool": { "filter": { "term": { "type": "student" } }, "should": [ { "term": { "name": "DebugST" } }, { "term": { "name": "None" } } ] } } }
You can see that the ES database query syntax is easily constructed from the POST request, and the Json data structure remains consistent. Multiple overloads are provided in Set and Append to make the logic more convenient, but of course they are just convenient syntax. The same can be achieved with if and for.
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" }, ... ]
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" ] } ]
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"
]
},
...
]
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() is used to sort data, internally by sorting by subsumption, and Sort has several overloads. However, the final version of Sort has the following signature, all overloads, and eventually calls this function.
public static STJson Sort(this STJson json, bool is_new_instance, STJsonSortCallback callback);
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] * *******************************************************************************/ [null,1,2,4,5,6,true,{"aa":"aa"}] [{"aa":"aa"},true,6,5,4,2,1,null]
Do you wonder why this is the case? But before that why don't you first wonder why you need such an array to sort? As you can see above Sort needs a callback function, the STJson extension provides two default callback functions which are constructed by the following code:
private static STJsonSortCallback BuildDefaultSortCallback(bool is_desc) { return (a, b) => { int n_ret = 0; if (a.IsNullOrNullValue()) { n_ret = -1; } else if (b.IsNullOrNullValue()) { n_ret = 1; } else if (a.IsNumber && b.IsNumber) { n_ret = Convert.ToDouble(a.Value) < Convert.ToDouble(b.Value) ? -1 : 1; } else if (a.ValueType != b.ValueType) { n_ret = a.ValueType - b.ValueType; } else { switch (a.ValueType) { case STJsonValueType.Boolean: n_ret = (bool)b.Value ? -1 : 1; break; case STJsonValueType.Datetime: n_ret = (DateTime)a.Value < (DateTime)b.Value ? -1 : 1; break; case STJsonValueType.String: n_ret = string.Compare(a.Value.ToString(), b.Value.ToString()); break; } } return is_desc ? -n_ret : n_ret; }; }
So if you don't have that weird array then everything will work fine. What if you really want to just sort the numbers in it?
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]
Sort by 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" ] } ]
Of course you can also specify a descending order:
Console.WriteLine(json_src.Sort("age", true).ToString(4));
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() 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").ForEach((item)=>item.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() 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() 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.
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 } ] }
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.