JSONPath และการใช้งานเบื้องต้นกับ Kubernetes
XML แม้จะเป็นภาษาที่เคร่งครัดและขี้บ่น มีแต่กฎหยุมหยิมเป็นจิ๋มช้างแอฟฟริกา แต่ก็มาพร้อมฟีเจอร์สุดชิคพิชิตใจเธอ หนึ่งในฟีเจอร์นั้นก็คือ XPath ความสามารถท่องโลกกว้างที่จะทำให้คุณเข้าถึงและดึงเฉพาะส่วนที่ต้องการจากก้อน XML นั้นได้
ทว่าโลกปัจจุบันนั้นขับเคลื่อนด้วย JSON หากเราอยากกรองบางชิ้นส่วนจากข้อมูลนี้ด้วยเทคนิคคล้าย XPath หละจะทำอย่างไร? นี่จึงเป็นที่มาของ JSONPath นั่นเอง
JSONPath นั้นคือ XPath for JSON
สมมติเรามีก้อนขอมูล JSON เช่นข้างล่างนี้
1{2 "articles": [3 {4 "id": 93,5 "type": "BlogPost",6 "slug": "guess-js",7 "title": "โหลดเพจถัดไปเร็วขึ้นด้วย Guess.js และ React",8 "thumbnail": "abc",9 "view_count": 4044,10 "created_at": "2018-05-11T13:41:16.806Z",11 "updated_at": "2018-05-17T11:12:06.132Z",12 "user": {13 "name": "Nuttavut Thongjor",14 "avatar": "abc"15 }16 },17 {18 "id": 92,19 "type": "BlogPost",20 "slug": "react-render-props",21 "title": "รู้จัก Render Props อีกทางเลือกนอกเหนือจาก HOC",22 "thumbnail": "abc",23 "view_count": 2868,24 "created_at": "2018-05-08T19:17:54.713Z",25 "updated_at": "2018-05-17T11:34:00.070Z",26 "user": {27 "name": "Nuttavut Thongjor",28 "avatar": "abc"29 }30 },31 {32 "id": 91,33 "type": "BlogPost",34 "slug": "nextjs-6",35 "title": "มีอะไรใหม่บ้างใน Next.js 6.0",36 "thumbnail": "abc",37 "view_count": 4638,38 "created_at": "2018-04-30T14:13:40.961Z",39 "updated_at": "2018-05-17T10:52:21.631Z",40 "user": {41 "name": "Nuttavut Thongjor",42 "avatar": "abc"43 }44 },45 {46 "id": 90,47 "type": "BlogPost",48 "slug": "react-2020",49 "title": "ปี 2020 โค๊ด React เราจะเปลี่ยนแปลงยังไง?",50 "thumbnail": "abc",51 "view_count": 5498,52 "created_at": "2018-04-29T18:54:03.205Z",53 "updated_at": "2018-05-17T09:33:24.442Z",54 "user": {55 "name": "Nuttavut Thongjor",56 "avatar": "abc"57 }58 },59 {60 "id": 89,61 "type": "BlogPost",62 "slug": "flexbox-and-auto-margins",63 "title": "จัดตำแหน่งใน Flexbox ด้วยพลานุภาพของ Auto Margins",64 "thumbnail": "abc",65 "view_count": 5170,66 "created_at": "2018-03-26T08:26:20.488Z",67 "updated_at": "2018-05-17T11:30:32.734Z",68 "user": {69 "name": "Nuttavut Thongjor",70 "avatar": "abc"71 }72 },73 {74 "id": 88,75 "type": "BlogPost",76 "slug": "skaffold",77 "title": "พัฒนาแอพบน Kubernetes ให้เป็นเรื่องง่ายด้วย Skaffold",78 "thumbnail": "abc",79 "view_count": 4966,80 "created_at": "2018-03-23T12:02:32.410Z",81 "updated_at": "2018-05-17T10:56:35.442Z",82 "user": {83 "name": "Nuttavut Thongjor",84 "avatar": "abc"85 }86 },87 {88 "id": 87,89 "type": "BlogPost",90 "slug": "storing-access-token-localstorage-vs-cookies",91 "title": "เข้าใจ Web Security: จัดเก็บ JWT ไว้ใน local storage หรือ cookies ดี?",92 "thumbnail": "abc",93 "view_count": 21227,94 "created_at": "2017-12-05T07:21:25.634Z",95 "updated_at": "2018-05-17T11:36:54.677Z",96 "user": {97 "name": "Nuttavut Thongjor",98 "avatar": "abc"99 }100 },101 {102 "id": 86,103 "type": "BlogPost",104 "slug": "being-better-programmers",105 "title": "5 วิธี สู่การเป็นโปรแกรมเมอร์ที่ดีกว่า",106 "thumbnail": "abc",107 "view_count": 10821,108 "created_at": "2017-12-03T13:10:43.213Z",109 "updated_at": "2018-05-17T11:06:33.310Z",110 "user": {111 "name": "Nuttavut Thongjor",112 "avatar": "abc"113 }114 }115 ],116 "meta": {117 "pagination": {118 "type": "articles",119 "prev_page": null,120 "next_page": 2,121 "current_page": 1122 }123 }124}
หากเราต้องการชื่อของผู้เขียนบทความแรก ด้วยความสามารถของ JavaScript เราสามารถเข้าถึงข้อมูลนี้ได้จากการเรียก articles[0].user.name
หรือ articles[0]['user']['name']
ซึ่งก็แทบไม่แตกต่างอะไรจากการเรียกใช้งาน XPath คือ /articles[1]/user/name
ในกรณีของการกรองข้อมูลที่ซับซ้อน เช่น ต้องการ title
ของบทความทั้งหมดออกมา ด้วยความสามารถของ JavaScript เรายังสามารถใช้ map
เพื่อคัดเฉพาะ title
ของบทความออกมาได้ ดังนี้
1articles.map(({ title }) => title)
แต่ช้าก่อน ลำพังจะเข้าเว็บ TCAS ไปสอบเข้ามหา'ลัยก็ยากพอแล้ว ชีวิตฉันต้องยากกับการ filter ข้อมูลด้วยฤานี่
ความยากในการเข้าถึงข้อมูลจะหมดไปเมื่อเราใช้ JSONPath... เชื่อเถอะ อุตสาห์เขียนเป็นบทความชวนเชื่อขนาดนี้แล้วนะ
รู้จัก Syntax ของ JSONPath
เพื่อให้การทดสอบไวยากรณ์เป็นไปอย่างราบลื่น เพื่อน ๆ ควรเปิดเว็บjsonpath.com พร้อมทำการคัดลอกข้อมูล JSON ข้างต้นใส่กล่องข้อความ เพื่อดำเนินการกรองข้อมูลด้วยไวยากรณ์ของ JSONPath ต่อไป
เรามาเริ่มกันที่อักขระแสดงตำแหน่งกันก่อนครับ
JSONPath นั้นมีอักษรแสดงตำแหน่งอยู่สองตัว ตัวแรกคือ $
ใช้สำหรับอ้างอิงถึง root element ซึ่งก็คือตัวก้อน JSON นั้นเอง เช่น เมื่อเราต้องการเข้าถึงชื่อของผู้เขียนของบทความแรก เราต้องบอกก่อนว่าจุดเริ่มต้นของการค้นหาให้เริ่มที่ root ด้วยการใส่ $ นำมาได้ออกมาเป็น $.articles[0].user.name
โดยให้ผลลัพธ์เป็น ["Nuttavut Thongjor"]
โปรดสังเกตว่าข้อมูลจาก JSONPath นั้นจะได้ออกมาเป็นอาร์เรย์ นั่นเพราะข้อมูลจากการกรองนั้นอาจมีได้มากกว่าหนึ่งตัว
อักขระอ้างอิงตำแหน่งตัวที่สองคือ @
ที่หมายถึง element ปัจจุบัน เช่น $.articles[(@.length - 1)]
เมื่อเราใส่ @
เข้าไปใน []
หลัง articles เจ้า @
ตัวนี้จะหมายถึง element ปัจจุบันซึ่งก็คือ articles นั่นเอง
@
นั้นมักใช้คู่กับการดำเนินการอื่น เช่น การระบุเงื่อนไขสำหรับการกรองข้อมูล เป็นต้น
JSONPath นั้นอนุญาตให้เราประมวลผลค่าใด ๆ ได้ด้วยการครอบทับด้วย ()
เช่น $.articles[(@.length - 1)]
เป็นการสั่งให้ประมวลผล @.length - 1
เสียก่อน โดย @.length - 1
หมายถึงการนับจำนวนบทความทั้งหมดแล้วลบออกหนึ่ง ซึ่งมีค่าเท่ากับตำแหน่งอาร์เรย์ของบทความตัวสุดท้าย หลังการประมวลผล ()
จึงมีค่าเป็น $.articles[7]
นั่นเอง
การใช้ ()
ก็ดูดีนะ แต่จะดีกว่านี้หากการใช้ ()
ไม่ได้แค่ประมวลผลค่า หากแต่ทำการกรองค่าให้กับเราด้วย JSONPath รู้ใจจึงเพิ่ม ?()
มาให้เพื่อใช้ทั้งประมวลผลค่าแล้วจึงกรองผลลัพธ์ออกมา เช่น $.articles[?(@.viewCount > 10000)]
เป็นการสั่งให้กรองเฉพาะบทความที่มีผู้เข้าชมมากกว่าหนึ่งหมื่นคน ได้ผลลัพธ์ออกมาดังนี้
1[2 {3 "id": 87,4 "type": "BlogPost",5 "slug": "storing-access-token-localstorage-vs-cookies",6 "title": "เข้าใจ Web Security: จัดเก็บ JWT ไว้ใน local storage หรือ cookies ดี?",7 "thumbnail": "abc",8 "viewCount": 21227,9 "created_at": "2017-12-05T07:21:25.634Z",10 "updated_at": "2018-05-17T11:36:54.677Z",11 "user": {12 "name": "Nuttavut Thongjor",13 "avatar": "abc"14 }15 },16 {17 "id": 86,18 "type": "BlogPost",19 "slug": "being-better-programmers",20 "title": "5 วิธี สู่การเป็นโปรแกรมเมอร์ที่ดีกว่า",21 "thumbnail": "abc",22 "viewCount": 10821,23 "created_at": "2017-12-03T13:10:43.213Z",24 "updated_at": "2018-05-17T11:06:33.310Z",25 "user": {26 "name": "Nuttavut Thongjor",27 "avatar": "abc"28 }29 }30]
นอกเหนือจากการใช้ ?()
เพื่อกำหนดเงื่อนไขในการกรองข้อมูลแล้ว JSONPath ยังสนับสนุนให้ทำการเลือกบางส่วนด้วยการกำหนดช่วงของอาร์เรย์ตามฟอร์แมต [start:end:step]
เช่น $.articles[1:3]
เป็นการบอกว่ากรองเอาเฉพาะบทความที่อยู่ใน index ที่ 1 จนถึง 2 (ไม่รวม 3) ของอาร์เรย์ หรือหากเป็น $.articles[1:]
ก็หมายถึงให้กรองบทความตั้งแต่ index ที่ 1 ไปจนสุดอาร์เรย์ หรือกล่าวอีกนัยได้ว่าไม่สนใจเฉพาะบทความแรกนั่นเอง
หากการเลือกบางส่วนจากอาร์เรย์ยังไม่จุใจ JSONPath ยังอนุญาตให้เราเลือก element จากอาร์เรย์ได้ด้วยการระบุ index ไปโดยตรงผ่าน [,]
เช่น $.articles[1,3,5]
ที่หมายถึงการกรองเอาเฉพาะบทความที่อยู่ใน index ที่ 1 3 และ 5 เท่านั้นนั่นเอง
แม้เราจะมีทั้งไวยากรณ์ดึงเฉพาะ subset ของอาร์เรย์ หรือดึงแค่อีลีเมนต์ที่สนใจออกมาแล้ว แต่ในบางสถานการณ์เราอาจต้องการทุก ๆ ค่าที่เป็นไปได้ออกมาด้วย สถานการณ์เช่นนี้สามารถใช้ *
ได้ เช่น $.articles[*].user.name
หมายถึงต้องการชื่อของผู้เขียนบทความ ทุกบทความ นั่นเอง
จากตัวอย่างก่อนหน้า หากทุกพร็อพเพอร์ตี้ของอ็อบเจ็กต์มีเพียงพร็อพเพอร์ตี้ของ user เท่านั้นที่มี name การเรียก $.articles[*].user.name
เพื่อดึงชื่อผู้แต่งของทุกบทความออกมาอาจต้องทำให้เราพิมพ์เยอะ
JSONPath ได้ทำการเตรียม ..
ไว้ให้เราเข้าถึงข้อมูลใดในระดับชั้นใดก็ได้ โดยไม่ต้องมานั่งไต่ระดับการเข้าถึงเช่นตัวอย่างก่อนหน้า เพื่อให้ตัวอย่างก่อนหน้าง่ายขึ้นเราจึงสามารถใช้เพียง $..name
ได้ครับ
1[2 "Nuttavut Thongjor",3 "Nuttavut Thongjor",4 "Nuttavut Thongjor",5 "Nuttavut Thongjor",6 "Nuttavut Thongjor",7 "Nuttavut Thongjor",8 "Nuttavut Thongjor",9 "Nuttavut Thongjor"10]
ตัวอย่างการใช้ JSONPath กับ Kubernetes
ทราบกันดีว่าเราสามารถใช้คำสั่ง kubectl get
ในการเข้าถึงรายละเอียดต่าง ๆ ได้ เช่น ใช้คำสั่ง kubectl get pods
เพื่อเข้าถึงรายละเอียดของ pod ต่าง ๆ เป็นต้น
สำหรับคำสั่ง get เราสามารถระบุ -o json
เพื่อให้การแสดงผลอยู่ในรูปแบบของ JSON ได้ เมื่อข้อมูลนี้แสดงผลด้วย JSON จึงไม่น่าแปลกที่จะใช้ JSONPath ในการกรองข้อมูลได้
นอกเหนือจาก -o json
คำสั่ง get ยังสนับสนุนการระบุ JSONPath ผ่าน -o=jsonpath
โดยการระบุ JSONPath นั้นต้องระบุผ่านการครอบด้วย {}
โดยเราสามารถละ $
ที่หมายถึง root element ออกได้ เพราะอ็อบเจ็กต์ไหน ๆ จุดเริ่มต้นของการค้นหาก็อยู่ที่ root element อยู่ดีนั่นเอง
สมมติให้ข้อมูลจากการสั่ง kubectl -o json
ของเราได้ผลลัพธ์ตามฟอร์แมต JSON ดังนี้
1{2 "apiVersion": "v1",3 "items": [4 {5 "apiVersion": "v1",6 "kind": "Node",7 "metadata": {8 "annotations": {9 "node.alpha.kubernetes.io/ttl": "0",10 "volumes.kubernetes.io/controller-managed-attach-detach": "true"11 },12 "creationTimestamp": "2018-05-17T12:50:31Z",13 "labels": {14 "beta.kubernetes.io/arch": "amd64",15 "beta.kubernetes.io/os": "linux",16 "kubernetes.io/hostname": "minikube"17 },18 "name": "minikube",19 "namespace": "",20 "resourceVersion": "207",21 "selfLink": "/api/v1/nodes/minikube",22 "uid": "e245c9a1-59d0-11e8-abd9-0242ac11002c"23 },24 "spec": {25 "externalID": "minikube"26 },27 "status": {28 "addresses": [29 {30 "address": "172.17.0.44",31 "type": "InternalIP"32 },33 {34 "address": "minikube",35 "type": "Hostname"36 }37 ],38 "allocatable": {39 "cpu": "2",40 "ephemeral-storage": "42586605088",41 "hugepages-2Mi": "0",42 "memory": "913520Ki",43 "pods": "110"44 },45 "capacity": {46 "cpu": "2",47 "ephemeral-storage": "46209424Ki",48 "hugepages-2Mi": "0",49 "memory": "1015920Ki",50 "pods": "110"51 }52 }53 }54 ],55 "kind": "List",56 "metadata": {57 "resourceVersion": "",58 "selfLink": ""59 }60}
หากเราสนใจข้อมูล CPU จาก capacity การจะมานั่งกรองข้อมูลจาก JSON คงไม่ใช่เรื่องดีเป็นแน่ แต่นั่นไม่ใช่ปัญหาเพราะเราสามารถระบุเป็น JSONPath เพื่อกรองข้อมูลได้ด้วยคำสั่ง kubectl get nodes -o=jsonpath='{.items[*].status.capacity.cpu}'
โดยจะได้ผลลัพธ์ออกมาเป็น 2 นั่นเอง
สรุป
เช่นเดียวกับ XPath ของ XML JSONPath นั้นก็เป็นประโยชน์สำหรับการกรองข้อมูลจาก JSON โดยเฉพาะอย่างยิ่งเมื่อข้อมูลค่อนข้างใหญ่จนใช้สายตากวาดมองไม่ทั่วถึง นอกจากนี้ JSONPath ยังถูกนำไปใช้เพื่อกรองข้อมูล JSON ควบคู่กับซอฟต์แวร์อื่นเช่น Kubernetes อีกด้วย
เอกสารอ้างอิง
JSONPath Support. Retrieved May, 17, 2018, from https://kubernetes.io/docs/reference/kubectl/jsonpath/
JSONPath - XPath for JSON. Retrieved May, 17, 2018, from http://goessner.net/articles/JsonPath/index.html#e2
สารบัญ
- JSONPath นั้นคือ XPath for JSON
- รู้จัก Syntax ของ JSONPath
- ตัวอย่างการใช้ JSONPath กับ Kubernetes
- สรุป
- เอกสารอ้างอิง