從JsonPath到SPL

2021-12-25 乾學院

JSON的多層結構可存儲豐富的信息,再加上體積小傳輸效率高,因此被廣泛應用於微服務、程序間通訊、配置文件等場景。但多層結構比二維結構格式複雜,計算起來難度很大,這就對JSON類庫提出了較高的要求。其中,Gson\Jackson等類庫不支持JSON計算語言,只提供了將JSON串解析為JAVA\C#對象的函數。這些類庫沒有計算能力,即使實現很簡單的條件查詢,也要編寫大量代碼,開發效率低且實用性差。

好消息是,JsonPath出現了。

與前面提到的類庫不同,JsonPath仿照XPath語法,提供了原始的JSON計算語言,可以用表達式查詢出符合條件的節點,並支持一些聚合計算。下面試舉幾例。

文件data.json存儲員工記錄以及員工的訂單,部分數據如下:

[  {"EId":2,"State":"NewYork  ","Dept":"Finance","Name":"Ashley","Gender":"F","Salary":11000,"Birthday":"1980-07-19","Orders":[]},{"EId":3,"State":"New   Mexico","Dept":"Sales","Name":"Rachel","Gender":"F","Salary":9000,"Birthday":"1970-12-17","Orders":[{"OrderID":32,"Client":"JFS","SellerId":3,"Amount":468.0,"OrderDate":"2009-08-13"},…{"OrderID":99,"Client":"RA","SellerId":3,"Amount":1731.2,"OrderDate":"2009-11-05"}]},…]

條件查詢:找到員工Rachel的所有訂單。JsonPath代碼如下:

File file = new File("D:\\json\\data.json");Long fileLength = file.length();byte[] fileContent = new byte[fileLength.intValue()];FileInputStream in = new FileInputStream(file);in.read(fileContent);in.close();String JsonStr= new String(fileContent, "UTF-8")Object document = Configuration.defaultConfiguration().jsonProvider().parse(JsonStr);ArrayList l=JsonPath.read(document,   "$[?(@.Name=='Rachel')].Orders");

上面代碼先從文件讀入字符串,再轉為JSON對象,然後進行查詢。具體的查詢表達式是 $[?(@.Name=='Rachel')].Orders,其中$代表根節點,即員工記錄(含訂單欄位),Orders代表下層的訂單記錄(即訂單欄位),上下層之間用點號隔開。每層節點後面可跟隨查詢條件,形如[?(…)],具體用法參考官網。

組合查詢:找出所有價格在1000-2000,且客戶名包含business字樣的訂單。關鍵代碼如下:

……l=JsonPath.read(document,   "$[*].Orders[?((@.Amount>1000 && @.Amount<2000) &&   @.Client =~ /.*?business.*?/i )]");

代碼中的(@.Amount>1000 && @.Amount<2000)是區間查詢條件,@.Client =~ /.*?business.*?/i是正則表達式的模糊查詢條件,&&是邏輯運算符「與」(|| 是「或」)。

聚合計算:統計所有訂單的總金額。關鍵代碼如下:

……Double   d=JsonPath.read(document, "$.sum($[*].Orders[*].Amount)");

從這些例子可以看出來,JsonPath的語法直觀易懂,可以用點號方便地訪問多層結構,可以用較短的代碼實現條件查詢,還能進行簡單的聚合計算。Gson\JacksonJsonPath實現了計算能力從無到有的突破,這要歸功於JSON計算語言。代碼中的sum是求和函數,類似的函數還有平均、max、min、計數。

有不表示強。實際上,JsonPath的JSON計算語言還比較原始,計算能力很弱。JsonPath只支持查詢和聚合這兩種很簡單的計算,不支持其他大多數基礎計算,離任意和自由的計算更是遙遠。要實現大多數基礎計算,JsonPath仍然要硬編碼實現。

分組匯總為例:對所有訂單按客戶分組,統計各組的訂單金額。關鍵代碼如下:

……ArrayList orders=JsonPath.read(document,   "$[*].Orders[*]");Comparator<HashMap> comparator = new   Comparator<HashMap>() {    public int   compare(HashMap record1, HashMap record2) {        if   (!record1.get("Client").equals(record2.get("Client"))) {              return   ((String)record1.get("Client")).compareTo((String)record2.get("Client"));        } else {              return   ((Integer)record1.get("OrderID")).compareTo((Integer)record2.get("OrderID"));        }    }};Collections.sort(orders, comparator);ArrayList<HashMap> result=new   ArrayList<HashMap>();HashMap currentGroup=(HashMap)orders.get(0);double sumValue=(double) currentGroup.get("Amount");for(int i = 1;i < orders.size(); i ++){    HashMap   thisRecord=(HashMap)orders.get(i);      if(thisRecord.get("Client").equals(currentGroup.get("Client"))){          sumValue=sumValue+(double)thisRecord.get("Amount");    }else{        HashMap   newGroup=new HashMap();          newGroup.put(currentGroup.get("Client"),sumValue);          result.add(newGroup);        currentGroup=thisRecord;          sumValue=(double) currentGroup.get("Amount");    }}System.out.println(result);

上述代碼先用JsonPath取出訂單列表,再將訂單列表按Client排序,取出第1條作為當前組的初值,然後依次循環剩餘的訂單。如果當前訂單與當前組相比Client不變,則將當前訂單的Amount累加到當前組;如果Client改變,則說明當前組已匯總完成。

JsonPath的計算能力很弱,不支持分組匯總,只能硬編碼完成大部分計算,這就要求程式設計師控制所有細節,代碼冗長且容易出錯。如果換一個分組欄位或匯總欄位,則要修改多處代碼,如果對多個欄位分組或匯總,代碼還需大量修改,這就很難寫出通用代碼。除了分組匯總,JsonPath不支持的基礎計算還有:重命名、排序、去重、關聯計算、集合計算、笛卡爾積、歸併計算、窗口函數、有序計算等。JsonPath也不支持將大計算目標分解為基礎計算的機制,比如子查詢、多步驟計算等。實際上,對大多數計算來說,JsonPath都要硬編碼完成。

除了計算能力之外,Jsonpath還有個問題,就是沒有自己的數據源接口,即使很簡單的文件JSON,也需要硬編碼實現。而JSON一般來自http restful,特殊些的會來自MongoDB或elasticSearch,只有引入第三方類庫或硬編碼才能從這些接口取數,這導致架構複雜、不穩定因素增大、開發效率降低。

JsonPath的JSON計算能力很弱,本質是因為其計算語言過於原始。要想提高JSON計算能力,必須使用更專業的計算語言。

SPL是個更好的選擇。

SPL是開源的結構化數據\半結構化數據計算語言,提供了豐富的類庫和精煉的語法,可以用簡短的代碼實現所有的基礎計算,可將大計算目標拆分為基礎計算,支持多種數據源接口,同時提供JDBC集成接口。

同樣的條件查詢,SPL代碼如下:


A1=json(file("d:\\json\\data.json").read())2=A1.select(Name=="Rachel").Orders

A1代碼從文件讀字符串,並轉為序表。序表是通用的結構化\半結構化數據對象,JSON是半結構化數據的一種。A2中函數select查詢出符合條件的員工記錄,Orders表示記錄的訂單欄位(訂單列表),上下層之間用.號隔開。


A1…. //省去JSON對象/序表生成過程2=A1.conj(Orders)3=A2.select((Amount>1000 &&   Amount<=2000) && like@c(Client,"*business*"))

A2合併所有員工的訂單,A3進行條件查詢,like函數用於模糊查詢字符串,@c表示不區分大小寫。這裡用到了多步驟計算,代碼邏輯更清晰,也可將A2A3合併為一句。


A1….2=A1.conj(Orders).sum(Amount)

代碼中的sum是求和函數,類似的函數還有avg\sum\min\count。

這段代碼可在SPL的IDE中調試/執行,也可存為腳本文件(比如getSum.dfx),通過JDBC接口在JAVA中調用,具體代碼如下:
package Test;  import java.sql.Connection;  import java.sql.DriverManager;  import java.sql.ResultSet;  import java.sql.Statement;  public class test1 {      public static void main(String[]   args)throws Exception {          Class.forName("com.esproc.jdbc.InternalDriver");          Connection connection   =DriverManager.getConnection("jdbc:esproc:local://");          Statement statement =   connection.createStatement();          ResultSet result =   statement.executeQuery("call getSum()");          printResult(result);          if(connection != null)   connection.close();      }…}

上面的用法類似存儲過程,其實SPL也支持類似SQL的用法,即無須腳本文件,直接將SPL代碼嵌入JAVA,代碼如下:

…ResultSet result =   statement.executeQuery("=json(file(\"D:\\data\\data.json\").read()).conj(Orders).sum(Amount)");…

SPL提供了豐富的庫函數,支持各種基礎計算,上面的查詢和聚合只是其中一部分,更多基礎計算如下:


A
1….
2=A1.conj(Orders).groups(Client;sum(Amount))分組匯總3=A1.groups(State,Gender;avg(Salary),count(1))多欄位分組匯總4=A1.new(Name,Gender,Dept,Orders.OrderID,Orders.Client,Orders.Client,Orders.SellerId,Orders.Amount,Orders.OrderDate)關聯5=A1.sort(Salary)排序6=A1.id(State)去重

SPL計算能力強,經常可以簡化多層json的計算,比如:文件JSONstr.json的runners欄位是子文檔,子文檔有3個欄位:horseId、ownerColours、trainer,其中trainer含有下級欄位trainerId ,ownerColours是逗號分割的數組。部分數據如下:

[   {      "race": {            "raceId":"1.33.1141109.2",            "meetingId":"1.33.1141109"      },      ...      "numberOfRunners": 2,      "runners": [        {       "horseId":"1.00387464",                "trainer": {                    "trainerId":"1.00034060"                },            "ownerColours":"Maroon,pink,dark blue."          },          {     "horseId":"1.00373620",                "trainer": {                    "trainerId":"1.00010997"                },            "ownerColours":"Black,Maroon,green,pink."          }]   },...]

現在要按 trainerId分組,統計每組中 ownerColours的成員個數,可用下面的SPL實現。


A1=json(file("/workspace/JSONstr.json").read())2=A1(1).runners3=A2.groups(trainer.trainerId; ownerColours.array().count():times)

SPL計算能力強,經常可以簡化複雜的JSON計算。比如通過http restful可取得按時間排序的每日值班情況,部分數據如下:

[{"Date":"2018-03-01","name":"Emily"},{"Date":"2018-03-02","name":"Emily"},{"Date":"2018-03-04","name":"Emily"},{"Date":"2018-03-04","name":"Johnson"},{"Date":"2018-04-05","name":"Ashley"},{"Date":"2018-03-06","name":"Emily"},{"Date":"2018-03-07","name":"Emily"}……]

一個人通常會持續值班幾個工作日,之後再換人,現在要依次計算出每個人連續的值班情況,結果輸出為二維表,部分結果如下:

namebeginendEmily2018-03-012018-03-03Johnson2018-03-042018-03-04Ashley2018-03-052018-03-05Emily2018-03-062018-03-07………

要獲得上述結果,應先將數據按照name進行有序分組,即如果連續幾條記錄的name都相同,則這幾條記錄分為同一組,直到name發生變化。注意這種分組可能會使同一個員工分出多組數據,比如Emily。分組後,應按日期取各組的首尾兩條,即所求的開始值班日期和結束值班日期。這裡涉及有序分組、分組後計算(即窗口函數)、按位置取值等難度較高的計算,常見的計算語言寫起來會很繁瑣,改用SPL就簡單多了。代碼如下:


A1=json(httpfile("http://127.0.0.1:6868/api/getDuty").read())2=duty.group@o(name)3=A2.new(name,~.m(1).date:begin,~.m(-1).date:end)

除了較強的計算能力之外,SPL還提供了豐富的數據源接口,除了上面提到的文件、restful,還支持MongoDB、elsticSearch等,詳情參考官網。

從Gson\Jackson到JsonPath,JSON計算語言從無到有,從JsonPath到SPL,JSON計算能力由弱到強,每一次質的飛躍,都驅動著開發效率的大幅提升。

簡單好用的SPL開源啦!

為了給感興趣的小夥伴們提供一個相互交流的平臺,

特地開通了交流群(群完全免費,不廣告不賣課)

相關焦點

  • 數據提取之JSON與JsonPATH
    ,因為大多數數據是不需要的,所以我們需要進行數據解析,常用的數據解析方式有正則表達式,xpath,bs4,這次我們來介紹一下另一個數據解析庫--jsonpath,在此之前我們需要先了解一下什麼是json。
  • 學會了JsonPath,你的Python接口腳本才算完整
    Jsonpath是跨語言的,很多語言都可以使用jsonpath,如Javascript,Python和PHP,Java。JsonPath提供的json解析非常強大,它提供了類似正則表達式的語法,基本上可以滿足所有你想要獲得的json內容。
  • Json解析神器
    來看看這個工具:依賴:<dependency>    <groupId>com.jayway.jsonpath</groupId>    <artifactId
  • 在Python裡玩轉JSON數據
    複雜一點的比如這種(後文會多次使用到這個例子):{    "animals": {        "dog": [            {大家別擔心,我們可以將數據複製到一些json插件或在線解析!比如這個插件:
  • 在Python中操縱json數據的最佳方式
    2.1 一個簡單的例子安裝完成後,我們首先來看一個簡單的例子,從而初探其使用方式:這裡使用到的示例json數據來自高德地圖步行導航接口,包含了從天安門廣場到西單大悅城的步行導航結果,原始數據如下,層次結構較深:
  • 8個例子掌握Python的Pathlib模塊
    #relative path to the current folder如果我們在沒有參數的情況下調用Path方法,它將創建一個到當前正在工作的文件夾的相對路徑(路徑可以描述為相對路徑或絕對路徑)。2. 通過傳遞文件夾和文件名創建路徑我們可以通過將文件夾和文件名作為單獨的字符串傳遞來調用path方法。
  • 快速了解Composer 的 Autoload與SPL Autoload源碼實現與區別
    任務是 composer 加載類的初始化 (頂級命名空間與文件路 徑映射初始化) 和註冊 (spl_autoload_register ())。ClassLoader.php:composer 加載類。composer 自動加載功能的核心類。
  • 生成labelme能查看的json格式文件
    從左上點開始順時針旋轉的四個頂點的坐標,如下圖所示:將TXT文檔中標註信息,存儲到labelme標註的json文件內,代碼如下:import jsonimport base64from PIL import Imageimport ioimport osimport cv2import numpy as np
  • Newtonsoft.Json 簡單擴展
    = JsonConvert.SerializeObject(obj, options);    return json;}序列化方法只有兩個簡單的參數,即對象的屬性名稱是否為駝峰式,及日期格式。public static T GetValue<T>(this string input,string path=""){ if(string.IsNullOrEmpty(path)) { return JsonConvert.DeserializeObject<T>(input); }
  • 13-python爬蟲之JSON操作
    轉化 python的類型,返回一個python的類型從json到python的類型轉化對照如下:import jsona="[1,2,3,4]"b='{"k1":1,"k2":2}'#當字符串為字典時{}外面必須是''單引號{}裡面必須是""雙引號print json.loads(a) [1
  • Python中JSON結構數據的高效增刪改操作
    添加微信號"CNFeffery"加入技術交流群❝本文示例代碼及文件已上傳至我的Github倉庫https://github.com
  • mysql field json MySQL JSON 類型數據操作
    ``,``one_or_all``,``search_str``[,``escape_char``[,``path``] ...])in set (0.00 sec)mysql>SELECTJSON_OVERLAPS("[1,3,5,7]","[2,6,8]");±---+|JSON_OVERLAPS("[1,3,5,7]", 「[2,6,8]」)|±---+|0|±---+1、查詢JSON中的數據用 column->path
  • 【TS】612- 了不起的 tsconfig.json 指南
    到這一步,就完成了這個簡單的示例,接下來會基於這個示例代碼,講解《七、常見配置示例》。四、tsconfig.json 文件結構介紹1. 按頂層屬性分類在 tsconfig.json 文件中按照頂層屬性,分為以下幾類:
  • 嘗試一下sql server2016裡面的json功能
    另外一個東西,sql server 2016能支持json 的解析和應用啦,雖然我不知道它的性能如何,先來一發測試一下功能 測試一下基本的,從查詢結果裡面構造一個json 的格式create table t1(ID int identity,name nvarchar(50),Chinese int ,Math int)insert into t1 values
  • xml及json解析
    •解析器採用SAX方式在解析某個XML文檔時,它只要解析到XML文檔的一個組成部分,都會去調用事件處理器的一個方法,解析器在調用事件處理器的方法時,會把當前解析到的xml文件內容作為方法的參數傳遞給事件處理器。
  • 【Python基礎】嵌套 JSON 秒變 Dataframe!
    其它操作記錄路徑除了像上面那樣傳遞results["issues"]列表之外,我們還使用record_path參數在JSON對象中指定列表的路徑。# 使用路徑而不是直接用results["issues"]pd.json_normalize(results, record_path="issues")[FIELDS]自定義分隔符還可以使用sep參數自定義嵌套結構連接的分隔符,比如下面將默認的「.」替換「-」。
  • 嵌套 JSON 秒變 Dataframe!
    像上面這樣,如果嵌套層級特別多,就需要自己手擼一個遞歸來實現了,因為每層嵌套都需要調用一個像上面解析並添加到新列的方法。其它操作記錄路徑除了像上面那樣傳遞results["issues"]列表之外,我們還使用record_path參數在JSON對象中指定列表的路徑。
  • JavaScript 實現 JSON 解析器
    編寫 JSON 解析器所需的知識和技術可以轉移到編寫 JS 解析器中。因此,讓我們開始編寫 JSON 解析器!理解語法如果您查看了規範頁面,會發現有2個圖。的語法:function fakeParseJSON(str) { let i = 0; function parseObject() { if (str[i] === '{') { i++; skipWhitespace(); // if it is not '}', // we take the path
  • 精品教程,用Pandas解析json格式的數據,建議收藏
    ,在列表中帶有多個json格式的數據json_list = [ {'學校': '清華大學', '地理位置': '北京', '排名': 1}, {'學校': '北京大學', '地理位置': '北京', '排名': 2},]pd.json_normalize(json_list)下面我們來看一個帶有多層json格式數據的對象
  • mysql支持原生json使用說明
    查詢JSON:查詢 json 中的數據用 column->path 的形式,其中對象類型 path 這樣表示 $.path, 而數組類型則是 $[index]查詢testproject表student欄位中json對象id為1的記錄:SELECT * FROM testproject WHERE student