Babel Coder

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

beginner

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

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

สารบัญ

JSONPath นั้นคือ XPath for JSON

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

{
	"articles": [{
		"id": 93,
		"type": "BlogPost",
		"slug": "guess-js",
		"title": "โหลดเพจถัดไปเร็วขึ้นด้วย Guess.js และ React",
		"thumbnail": "abc",
		"view_count": 4044,
		"created_at": "2018-05-11T13:41:16.806Z",
		"updated_at": "2018-05-17T11:12:06.132Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}, {
		"id": 92,
		"type": "BlogPost",
		"slug": "react-render-props",
		"title": "รู้จัก Render Props อีกทางเลือกนอกเหนือจาก HOC",
		"thumbnail": "abc",
		"view_count": 2868,
		"created_at": "2018-05-08T19:17:54.713Z",
		"updated_at": "2018-05-17T11:34:00.070Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}, {
		"id": 91,
		"type": "BlogPost",
		"slug": "nextjs-6",
		"title": "มีอะไรใหม่บ้างใน Next.js 6.0",
		"thumbnail": "abc",
		"view_count": 4638,
		"created_at": "2018-04-30T14:13:40.961Z",
		"updated_at": "2018-05-17T10:52:21.631Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}, {
		"id": 90,
		"type": "BlogPost",
		"slug": "react-2020",
		"title": "ปี 2020 โค๊ด React เราจะเปลี่ยนแปลงยังไง?",
		"thumbnail": "abc",
		"view_count": 5498,
		"created_at": "2018-04-29T18:54:03.205Z",
		"updated_at": "2018-05-17T09:33:24.442Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}, {
		"id": 89,
		"type": "BlogPost",
		"slug": "flexbox-and-auto-margins",
		"title": "จัดตำแหน่งใน Flexbox ด้วยพลานุภาพของ Auto Margins",
		"thumbnail": "abc",
		"view_count": 5170,
		"created_at": "2018-03-26T08:26:20.488Z",
		"updated_at": "2018-05-17T11:30:32.734Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}, {
		"id": 88,
		"type": "BlogPost",
		"slug": "skaffold",
		"title": "พัฒนาแอพบน Kubernetes ให้เป็นเรื่องง่ายด้วย Skaffold",
		"thumbnail": "abc",
		"view_count": 4966,
		"created_at": "2018-03-23T12:02:32.410Z",
		"updated_at": "2018-05-17T10:56:35.442Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}, {
		"id": 87,
		"type": "BlogPost",
		"slug": "storing-access-token-localstorage-vs-cookies",
		"title": "เข้าใจ Web Security: จัดเก็บ JWT ไว้ใน local storage หรือ cookies ดี?",
		"thumbnail": "abc",
		"view_count": 21227,
		"created_at": "2017-12-05T07:21:25.634Z",
		"updated_at": "2018-05-17T11:36:54.677Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}, {
		"id": 86,
		"type": "BlogPost",
		"slug": "being-better-programmers",
		"title": "5 วิธี สู่การเป็นโปรแกรมเมอร์ที่ดีกว่า",
		"thumbnail": "abc",
		"view_count": 10821,
		"created_at": "2017-12-03T13:10:43.213Z",
		"updated_at": "2018-05-17T11:06:33.310Z",
		"user": {
			"name": "Nuttavut Thongjor",
			"avatar": "abc"
		}
	}],
	"meta": {
		"pagination": {
			"type": "articles",
			"prev_page": null,
			"next_page": 2,
			"current_page": 1
		}
	}
}

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

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

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

[
  {
    "id": 87,
    "type": "BlogPost",
    "slug": "storing-access-token-localstorage-vs-cookies",
    "title": "เข้าใจ Web Security: จัดเก็บ JWT ไว้ใน local storage หรือ cookies ดี?",
    "thumbnail": "abc",
    "viewCount": 21227,
    "created_at": "2017-12-05T07:21:25.634Z",
    "updated_at": "2018-05-17T11:36:54.677Z",
    "user": {
      "name": "Nuttavut Thongjor",
      "avatar": "abc"
    }
  },
  {
    "id": 86,
    "type": "BlogPost",
    "slug": "being-better-programmers",
    "title": "5 วิธี สู่การเป็นโปรแกรมเมอร์ที่ดีกว่า",
    "thumbnail": "abc",
    "viewCount": 10821,
    "created_at": "2017-12-03T13:10:43.213Z",
    "updated_at": "2018-05-17T11:06:33.310Z",
    "user": {
      "name": "Nuttavut Thongjor",
      "avatar": "abc"
    }
  }
]

นอกเหนือจากการใช้ ?() เพื่อกำหนดเงื่อนไขในการกรองข้อมูลแล้ว 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 ได้ครับ

[
  "Nuttavut Thongjor",
  "Nuttavut Thongjor",
  "Nuttavut Thongjor",
  "Nuttavut Thongjor",
  "Nuttavut Thongjor",
  "Nuttavut Thongjor",
  "Nuttavut Thongjor",
  "Nuttavut Thongjor"
]

ตัวอย่างการใช้ 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 ดังนี้

{
	"apiVersion": "v1",
	"items": [{
		"apiVersion": "v1",
		"kind": "Node",
		"metadata": {
			"annotations": {
				"node.alpha.kubernetes.io/ttl": "0",
				"volumes.kubernetes.io/controller-managed-attach-detach": "true"
			},
			"creationTimestamp": "2018-05-17T12:50:31Z",
			"labels": {
				"beta.kubernetes.io/arch": "amd64",
				"beta.kubernetes.io/os": "linux",
				"kubernetes.io/hostname": "minikube"
			},
			"name": "minikube",
			"namespace": "",
			"resourceVersion": "207",
			"selfLink": "/api/v1/nodes/minikube",
			"uid": "e245c9a1-59d0-11e8-abd9-0242ac11002c"
		},
		"spec": {
			"externalID": "minikube"
		},
		"status": {
			"addresses": [{
					"address": "172.17.0.44",
					"type": "InternalIP"
				},
				{
					"address": "minikube",
					"type": "Hostname"
				}
			],
			"allocatable": {
				"cpu": "2",
				"ephemeral-storage": "42586605088",
				"hugepages-2Mi": "0",
				"memory": "913520Ki",
				"pods": "110"
			},
			"capacity": {
				"cpu": "2",
				"ephemeral-storage": "46209424Ki",
				"hugepages-2Mi": "0",
				"memory": "1015920Ki",
				"pods": "110"
			}
		}
	}],
	"kind": "List",
	"metadata": {
		"resourceVersion": "",
		"selfLink": ""
	}
}

หากเราสนใจข้อมูล 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


แสดงความคิดเห็นของคุณ


No any discussions