Java 中 Snack3的使用

網(wǎng)上看了一篇Java 中 Gson的使用,所以也跟著寫篇Java 中 Snack3的使用

JSON 是一種文本形式的數(shù)據(jù)交換格式,從Ajax的時候開始流行,它比XML更輕量、比二進(jìn)制容易閱讀和編寫;解析和生成的方式很多,Java中最常用的類庫有:JSON-Java、Gson、Jackson、FastJson、Snack3等。

Snack3 基于jdk8,60kb大小,非常小巧。

<dependency>
  <groupId>org.noear</groupId>
  <artifactId>snack3</artifactId>
  <version>3.1.9</version>
</dependency>

Snack3 借鑒了 Javascript 所有變量由 var 申明,及 Xml dom 一切都是 Node 的設(shè)計(jì)。其下一切數(shù)據(jù)都以ONode表示,ONode也即 One node 之意,代表任何類型,也可以轉(zhuǎn)換為任何類型。

  • 強(qiáng)調(diào)文檔樹的操控和構(gòu)建能力
  • 做為中間媒體,方便不同格式互轉(zhuǎn)
  • 高性能Json path查詢(兼容性和性能很贊)
  • 支持序列化、反序列化

一、Snack3的基本用法

Snack3提供了幾個快捷函數(shù):

  • load(strOrObj), loadStr(str), loadObj(obj) 用于解析和加載;
  • stringify(obj), serialize(obj), deserialize(str,clz) 用于序列化和反序列化

(1)基本數(shù)據(jù)類型的解析

int i = ONode.load("100").getInt(); //100
double d = ONode.load("\"99.99\"").getDouble();  //99.99
boolean b = ONode.load("true").getBoolean();     // true
String str = ONode.load("String").getString();   // String

(2)基本數(shù)據(jù)類型的生成

String jsonNumber = ONode.load(100).toJson();       // 100
String jsonBoolean = ONode.load(false).toJson();    // false
String jsonString = ONode.load("String").toString(); //"String"

(3)POJO類的生成與解析

public class User {
    public String name;
    public int age;
    public String emailAddress;
}
生成JSON:
User user = new User("張三",24);

//輸出: {"name":"張三","age":24}
String json = ONode.stringify(user); //JSON字符化

//輸出: {"@type":"demo.User","name":"\u5F20\u4E09","age":24}
String json2 = ONode.serialize(user); //JSON序列化
解析并反序列化JSON:
String json = "{name:'張三',age:24}";
User user = ONode.deserialize(json, User.class);//JSON反序列化

二、序列化事項(xiàng)補(bǔ)充說明

從上面示例可以看出json的字段和值是的名稱和類型是一一對應(yīng)的,Snack3不支持直接改名稱,但可以通過transient關(guān)鍵字進(jìn)行排序,例:

public class User {
    public String name;
    public int age;
    public String emailAddress;
    public transient Date date;
}

或者,加載后再重改名稱,例:

User user = new User("name", 12, "xxx@mail.cn");

//輸出: {"name":"name","age":12,"email":"xxx@mail.cn"}
ONode.load(user).rename("emailAddress","email").toJson();

//o.rename(key,newKey); //重命名子節(jié)點(diǎn),并返回自己

Snack3在序列化和反序列化時需要使用反射,且只對字段進(jìn)行序列化(不管屬性)。

特性總結(jié):
  • 只對字段進(jìn)行序列化(包括私有)
  • 使用transient 對字段排序
  • 加載后可進(jìn)行重命名字段

三、Snack3中使用泛型

例如:JSON字符串?dāng)?shù)組:["Android","Java","PHP"]

當(dāng)要通過Snack3解析這個json時,一般有三種方式:使用ONode,使用數(shù)組,使用List;而List對于增刪都是比較方便的,所以實(shí)際使用是還是List比較多

數(shù)組比較簡單:

String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
String[] strings = ONode.deserialize(jsonArray,String[].class);

對于List將上面的代碼中的 String[].class 直接改為 List<String>.class 是不行的,對于Java來說List<String>List<User> 這倆個的字節(jié)碼文件只一個那就是List.class,這是Java泛型使用時要注意的問題 泛型擦除。

為了解決的上面的問題,Snack3提供了2種方式來實(shí)現(xiàn)對泛型的支持,所以將以上的數(shù)據(jù)解析為List<String>時需要這樣寫:

String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
ONode ary0            = ONode.load(jsonArray);
List<String> ary1 = ONode.deserialize(jsonArray,(new ArrayList<String>(){}).getClass());
List<String> ary2 = ONode.deserialize(jsonArray,(new TypeRef<List<String>>(){}).getClass());

//(new ArrayList<String>(){}).getClass()            //方式1,通過臨時類形(最終都是產(chǎn)生Class)
//(new TypeRef<List<String>>(){}).getClass()    //方式2,通過TypeRef(最終都是產(chǎn)生Class)

泛型的引入還可以減少無關(guān)的代碼:

{"code":"0","message":"success","data":{}}
{"code":"0","message":"success","data":[]}

像上面這段代碼,code只使用一次,message則幾乎不用;我們真正需要的data所包含的數(shù)據(jù),但它可能對象也可能是數(shù)組。如果Snack3不支持泛型或不知道Snack3支持泛型的同學(xué)一定會這么定義POJO:

public class UserResponse {
    public int code;
    public String message;
    public User data;
}

當(dāng)設(shè)計(jì)其它接口的時候又重新定義一個XxxResponse將data的類型改成Xxx,很明顯code,和message被重復(fù)定義了多次,會產(chǎn)大量的POJO。。。通過泛型可以將code和message字段抽取到一個Result的類中,這樣只需要編寫data字段所對應(yīng)的POJO即可:

public class Result<T> {
    public int code;
    public String message;
    public T data;
} 

四、Snack3的序列化與反序列化

(1)自動方式

Snack3提供了serialize(obj)deserialize(str,clz) 前者實(shí)現(xiàn)序列化,后者實(shí)現(xiàn)了反序列化。

ONode.serialize(obj);       //序列化
ONode.deserialize(str,clz); //反序列化

(2)手動方式:

手動的方式使用load(obj),toObject(clz),toJson() 來手動實(shí)現(xiàn)序列化和反序列化:

String json = "{\"name\":\"張三\",\"age\":\"24\"}";

//反序列化
User user = ONode.load(json,Constants.serialize()).toObject(User.class);

//序列化
ONode.load(user,Constants.serialize()).toJson();

自動方式最終都是通過load(obj),toObject(clz),toJson() 進(jìn)行操作。

內(nèi)部代碼:

/**
 * 序列化為 string(由序列化器決定格式)
 *
 * @param source java object
 * @throws Exception
 */
public static String serialize(Object source) {
    //加載java object,須指定Fromer
    return load(source, Constants.serialize(), DEFAULTS.DEF_OBJECT_FROMER).toJson();
}


/**
 * 反序列化為 java object(由返序列化器決定格式)
 *
 * @param source string
 * @throws Exception
 */
public static <T> T deserialize(String source, Class<?> clz) {
    //加載String,不需指定Fromer
    return load(source,  Constants.serialize(), null).toObject(clz);
}

五、使用Snack3導(dǎo)出null值、格式化輸出、日期時間

一般情況下ONode類提供的 API已經(jīng)能滿足大部分的使用場景,但有時需要更多特殊、強(qiáng)大的功能時,這時候就引入一個新的類 Constants。

Constants從名字上看它是一個提供配置的類,要想改變ONode默認(rèn)的設(shè)置必須使用該類進(jìn)行配置。用法:

Constants.of(..)                      //全新定義一份配置
Constants.def().add(..).sub(..)       //在默認(rèn)配置基礎(chǔ)上,添加或減少特性
Constants.serialize().add(..).sub(..) //在序列化配置基礎(chǔ)上,添加或減少特性

(1)Snack3在默認(rèn)情況下是不動導(dǎo)出值null的鍵的,如:

User user = new User("張三", 24);
System.out.println(ONode.stringify(user)); //{"name":"張三","age":24}


Constants cfg = Constants.def().add(Feature.SerializeNulls); //導(dǎo)出null
System.out.println(ONode.load(user, cfg).toJson()); //{"name":"張三","age":24,"emailAddress":null}

(2)格式化輸出、日期時間及其它:

Date date = new Date();

Constants cfg = Constants.of(Feature.WriteDateUseFormat) //使用格式化特性
        .build(c-> c.date_format = new SimpleDateFormat("yyyy-MM-dd",c.locale)); //設(shè)置格式符(默認(rèn)為:"yyyy-MM-dd'T'HH:mm:ss")

System.out.println(ONode.load(date, cfg).toJson()); //2019-12-06

六、使用Snack3進(jìn)行JSONPath查詢

在網(wǎng)上找了一份經(jīng)典的JSON樣本:

{
    "store": {
        "bicycle": {
            "color": "red",
            "price": 19.95
        },
        "book": [
            {
                "author": "劉慈欣",
                "price": 8.95,
                "category": "科幻",
                "title": "三體"
            },
            {
                "author": "itguang",
                "price": 12.99,
                "category": "編程語言",
                "title": "go語言實(shí)戰(zhàn)"
            }
        ]
    }
}

Snack3可以提供高速的JSONPath查詢,JSONPath更給日常的查詢節(jié)省了大量代碼:

ONode o = ONode.load(jsonStr);

//得到所有的書
ONode books = o.select("$.store.book");
System.out.println("books=::" + books);

//得到所有的書名
ONode titles = o.select("$.store.book.title");
System.out.println("titles=::" + titles);

//第一本書title
ONode title = o.select("$.store.book[0].title");
System.out.println("title=::" + title);

//price大于10元的book
ONode list = o.select("$.store.book[?(price > 10)]");
System.out.println("price大于10元的book=::" + list);

//price大于10元的title
ONode list2 = o.select("$.store.book[?(price > 10)].title");
System.out.println("price大于10元的title=::" + list2);

//category(類別)為科幻的book
ONode list3 = o.select("$.store.book[?(category == '科幻')]");
System.out.println("category(類別)為科幻的book=::" + list3);


//bicycle的所有屬性值
ONode values = o.select("$.store.bicycle.*");
System.out.println("bicycle的所有屬性值=::" + values);


//bicycle的color和price屬性值
ONode read = o.select("$.store.bicycle['color','price']");
System.out.println("bicycle的color和price屬性值=::" + read);

(1)支持的JSONPath語法

  • 字符串使用單引號,例:['name']
  • 過濾操作用空隔號隔開,例:[?(@.type == 1)]
支持操作 說明
$ 表示根元素
@ 當(dāng)前節(jié)點(diǎn)(做為過濾表達(dá)式的謂詞使用)
* 通用配配符,可以表示一個名字或數(shù)字。
.. 深層掃描。 可以理解為遞歸搜索。
.<name> 表示一個子節(jié)點(diǎn)
['<name>' (, '<name>')] 表示一個或多個子節(jié)點(diǎn)
[<number> (, <number>)] 表示一個或多個數(shù)組下標(biāo)(負(fù)號為倒數(shù))
[start:end] 數(shù)組片段,區(qū)間為[start,end),不包含end(負(fù)號為倒數(shù))
[?(<expression>)] 過濾表達(dá)式。 表達(dá)式結(jié)果必須是一個布爾值。
支持過濾操作符 說明
== left等于right(注意1不等于'1')
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于
=~ 匹配正則表達(dá)式[?(@.name =~ /foo.*?/i)]
in 左邊存在于右邊 [?(@.size in ['S', 'M'])]
nin 左邊不存在于右邊
支持尾部函數(shù) 說明
min() 計(jì)算數(shù)字?jǐn)?shù)組的最小值
max() 計(jì)算數(shù)字?jǐn)?shù)組的最大值
avg() 計(jì)算數(shù)字?jǐn)?shù)組的平均值
sum() 計(jì)算數(shù)字?jǐn)?shù)組的匯總值(新加的)

像這兩種寫法的語義是差不多:

$.store.book[0].title //建議使用這種
$['store']['book'][0]['title']

(2)語法示例說明

JSONPath 說明
$ 根對象
$[-1] 最后元素
$[:-2] 第0個至倒數(shù)第2個
$[1:] 第1個之后所有元素(0為首個)
$[1,2,3] 集合中1,2,3個元素(0為首個)

七、數(shù)據(jù)格式互轉(zhuǎn)

Snack3是采用(Fromer)->(ONode)->(Toer)的架構(gòu)。非常適合格式的轉(zhuǎn)換,開發(fā)時只需要完成與ONode的對接即可:

(1)將Xml轉(zhuǎn)為Ymal

String xml = "<xml>....</xml>";
XmlFromer xmlFromer = new XmlFromer();
YmalToer  ymalToer  = new YmalToer();

//加載xml,輸出ymal
String ymal = ONode.load(xml,Constants.def(),xmlFromer).to(ymalToer);

(2)加載Xml,去掉手機(jī)號,轉(zhuǎn)為java object

ONode tmp =ONode.load(xml,Constants.def(),xmlFromer);

//找到有手機(jī)號的,然后移除手機(jī)號
tmp.select("$..[?(@.mobile)]").forEach(n->n.remove("mobile"));

XxxModel m =tmp.toObject(XxxModel.class);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容