JSONPath และการใช้งานเบื้องต้นกับ Kubernetes

Nuttavut Thongjor

XML แม้จะเป็นภาษาที่เคร่งครัดและขี้บ่น มีแต่กฎหยุมหยิมเป็นจิ๋มช้างแอฟฟริกา แต่ก็มาพร้อมฟีเจอร์สุดชิคพิชิตใจเธอ หนึ่งในฟีเจอร์นั้นก็คือ XPath ความสามารถท่องโลกกว้างที่จะทำให้คุณเข้าถึงและดึงเฉพาะส่วนที่ต้องการจากก้อน XML นั้นได้

ทว่าโลกปัจจุบันนั้นขับเคลื่อนด้วย JSON หากเราอยากกรองบางชิ้นส่วนจากข้อมูลนี้ด้วยเทคนิคคล้าย XPath หละจะทำอย่างไร? นี่จึงเป็นที่มาของ JSONPath นั่นเอง

JSONPath นั้นคือ XPath for JSON

สมมติเรามีก้อนขอมูล JSON เช่นข้างล่างนี้

Code
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": 1
122 }
123 }
124}

หากเราต้องการชื่อของผู้เขียนบทความแรก ด้วยความสามารถของ JavaScript เราสามารถเข้าถึงข้อมูลนี้ได้จากการเรียก articles[0].user.name หรือ articles[0]['user']['name'] ซึ่งก็แทบไม่แตกต่างอะไรจากการเรียกใช้งาน XPath คือ /articles[1]/user/name

ในกรณีของการกรองข้อมูลที่ซับซ้อน เช่น ต้องการ title ของบทความทั้งหมดออกมา ด้วยความสามารถของ JavaScript เรายังสามารถใช้ map เพื่อคัดเฉพาะ title ของบทความออกมาได้ ดังนี้

JavaScript
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)] เป็นการสั่งให้กรองเฉพาะบทความที่มีผู้เข้าชมมากกว่าหนึ่งหมื่นคน ได้ผลลัพธ์ออกมาดังนี้

Code
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 ได้ครับ

Code
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 ดังนี้

Code
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
  • สรุป
  • เอกสารอ้างอิง