[Open Source] A lightweight, high-performance JsonPath framework, a powerful tool for parsing JSON in Java
![[Open Source] A lightweight, high-performance JsonPath framework, a powerful tool for parsing JSON in Java](https://yy123.ink/api/img/1782607011492-sr9tcf.jpg)
1. Project Overview
Snack3 for java
A High-Performance JsonPath Framework
Based on JDK 8, with a size of 80 KB. Supports serialization and deserialization, parsing and conversion, construction, searching, and JsonPath queries.
2. Open Source License
Licensed under Apache-2.0
3. Screenshots

4. Feature Overview
Snack3 draws inspiration from JavaScript, where all variables are declared using var, and XML DOM, where everything is a Node. In Snack3, all data is represented by ONode, which stands for One Node. ONode represents any data type and can also be converted into any type.
- Emphasizes document tree manipulation and construction capabilities
- High-performance JsonPath queries (excellent compatibility and performance)
- Also supports serialization and deserialization
- Implemented using no-argument constructors + fields (eliminating the risk of actions being triggered by injection)
5. Tech Stack
<dependency> <groupId>org.noear</groupId> <artifactId>snack3</artifactId> <version>3.2.74</version> </dependency>
Performance Testing
Execution time for each expression, running 1,000,000 times:
JsonPath Expression
Data
fastjson2
json-path
snack3
$..a
A
764ms
2633ms
225ms
$..*
A
(Not compatible 1)
3220ms
315ms
$.data.list[1,4]
A
524ms
782ms
133ms
$.data.list[1:4]
A
367ms
941ms
145ms
$.data.ary2[1].a
A
329ms
663ms
84ms
$.data.ary2[*].b.c
A
642ms
1050ms
237ms
$..b[?(@.c == 12)]
B
(Not compatible 2)
5636ms
535ms
$..c.min()
B
(Not compatible 2)
(Not compatible 2)
282ms
$[?(@.c =~ /a+/)]
C
(Not compatible 2)
3591ms
429ms
$..ary2[0].a
A
735ms
2551ms
311ms
$.data.list[?(@ in $..ary2[0].a)]
A
(Not compatible 2)
5483ms
674ms
For details, see:
- Article_JsonPath_Performance_Test.md
- Article_JsonPath_Performance_Test_2022.md
Here are a few examples
//demo0::String conversion
String json = ONode.stringify(user);
//Pretty-formatted string conversion
String json = ONode.load(user, Feature.PrettyFormat).toJson();
//demo1::Serialization
// -- Output with @type
String json = ONode.serialize(user);
//demo2::Deserialization
// -- JSON already contains @type
UserModel user = ONode.deserialize(json);
// -- JSON can omit @type (clz is declared)
UserModel user = ONode.deserialize(json, UserModel.class);
// -- JSON can omit @type, using generic type output (the type is known)
List<UserModel> list = ONode.deserialize(json, (new ArrayList<UserModel>(){}).getClass());
//demo3::Convert to ONode
ONode o = ONode.loadStr(json); //Convert JSON String to ONode
ONode o = ONode.loadObj(user); //Convert Java Object to ONode
//demo3.1::Convert to ONode and serialize a child node
ONode o = ONode.loadStr(json);
UserModel user = o.get("user").toObject(UserModel.class);
//demo4:Build JSON data (JPush REST API call)
public static void push(Collection<String> alias_ary, String text) {
ONode data = new ONode().build((d)->{
d.getOrNew("platform").val("all");
d.getOrNew("audience").getOrNew("alias").addAll(alias_ary);
d.getOrNew("options")
.set("apns_production",false);
d.getOrNew("notification").build(n->{
n.getOrNew("ios")
.set("alert",text)
.set("badge",0)
.set("sound","happy");
});
});
String message = data.toJson();
String author = Base64Util.encode(appKey+":"+masterSecret);
Map<String,String> headers = new HashMap<>();
headers.put("Content-Type","application/json");
headers.put("Authorization","Basic "+author);
HttpUtil.postString(apiUrl, message, headers);
}
//demo5:Get values
o.get("name").getString();
o.get("num").getInt();
o.get("list").get(0).get("lev").getInt();
//demo5.1::Get a value and convert it
UserModel user = o.get("user").toObject(UserModel.class); //Get the user node and convert it to UserModel
//demo5.2::Get or create a node and populate it
o.getOrNew("list2").fill("[1,2,3,4,5,5,6]");
//demo6::JSONPath //When the number of returned results is uncertain, an array type is returned
//Find all mobile numbers starting with 187, change them to 186, and output the modified JSON
o.select("$..mobile[?(@ =~ /^187/)]").forEach(n->n.val("186")).toJson();
//Find the mobile field under data.list[1] and convert it to long
o.select("$.data.list[1].mobile").getLong();
//Find all mobile numbers and convert them to List<String>
List<String> list = o.select("$..mobile").toObject(List.class);
//Find all mobile numbers under data.list and convert them to List<String>
List<String> list = o.select("$.data.list[*].mobile").toObject(List.class);
//Find users with mobile numbers starting with 187 and output List<UserModel>
List<UserModel> list = o.select("$.data.list[?(@.mobile =~ /^187/)]")
.toObjectList(UserModel.class);
//Or
List<UserModel> list = o.select("$.data.list[?(@.mobile =~ /^187/)]")
.toObjectList(UserModel.class);
//demo7:Iteration
//If it is an Object
o.forEach((k,v)->{
//...
});
//If it is an Array
o.forEach((v)->{
//...
});
//demo8:Custom encoding (addDecoder adds a parser for deserialization)
Options options = Options.def();
options.addEncoder(Date.class, (data, node) -> {
node.val().setString(DateUtil.format(data, "yyyy-MM-dd"));
});
String json = ONode.loadObj(orderModel, options).toJson();
Serialization Features
Object (can include type)
{"a":1,"b":"2"}
//or
{"@type":"...","a":1,"b":"2"}
Array
[1,2,3]
//or
[{"@type":"...","a":1,"b":"2"},{"@type":"...","a":2,"b":"10"}]
Annotation Customization
public enum BookType {
NOVEL(2,"Novel"),
CLASSICS(3,"Classics"),
;
@ONodeAttr public final int code; //Use code as the serialized field
public final String des;
BookType(int code, String des){this.code=code; this.des=des;}
}
public class Book {
String name;
BookType type;
@ONodeAttr(serialize=false) String author; //Do not serialize
@ONodeAttr(format="yyyy-MM-dd") Date releaseTime; //Format date output
}
Custom Encoding and Decoding
Options options = Options.def();
options.addEncoder(Date.class, (data, node) -> {
node.val().setString(DateUtil.format(data, "yyyy-MM-dd"));
});
options.addDecoder(Date.class, ...);
String json = ONode.loadObj(orderModel, options).toJson();
JSONPath Support
- Use single quotes for strings, e.g.: ['name']
- Separate filter operations with spaces, e.g.: [?(@.type == 1)]
Supported Operations
Description
$
Represents the root element
@
Current node (used as a predicate in filter expressions)
*
Universal wildcard, representing a name or number.
..
Deep scan. Can be understood as recursive search.
.<name>
Represents a child node
['<name>' (, '<name>')]
Represents one or more child nodes
[<number> (, <number>)]
Represents one or more array indexes (negative numbers count backward)
[start:end]
Array slice, range is [start,end), excluding end (negative numbers count backward)
[?(<expression>)]
Filter expression. The expression result must be a Boolean value.
[?(@.<name>)]
Filter expression. Indicates that a child node exists.
Supported filter operators (spaces are required on both sides of the operator)
Description
==
left equals right (note that 1 is not equal to '1')
!=
Not equal to
<
Less than
<=
Less than or equal to
>
Greater than
>=
Greater than or equal to
=~
Matches a regular expression [?(@.name =~ /foo.*?/i)]
in
Left operand exists in right operand [?(@.size in ['S', 'M'])]
nin
Left operand does not exist in right operand
Supported terminal functions
Description
min()
Calculate the minimum value of a numeric array
max()
Calculate the maximum value of a numeric array
avg()
Calculate the average value of a numeric array
sum()
Calculate the sum of a numeric array (newly added)
Example: n.select("$.store.book[0].title") or n.select("$['store']['book'][0]['title']")
Example: n.select("$..book.price.min()") //Find the lowest price
Snack3 API Dictionary
- Article_Snack3_API_Dictionary.md