JSON เป็น file format ที่เจอกันบ่อยมากเลยฮะ ผมเคยเขียนถึงไปแล้วในบล็อกนี้ สำหรับการจัดการ file นี้มีหลายวิธีมากๆ สำหรับบล็อกนี้เลยขอเล่าถึงวิธีบ้านๆ แต่ง่ายและมีประสิทธิภาพดีเลยฮะ แค่เราใช้ command line ด้วย package "jq" ฮะ

ถ้าสนใจ Bash script และ command line สามารถอ่านต่อได้ที่บล็อกนี้ได้เลยนะฮะ


jq คืออะไร

jq เป็น package ใช้ใน command line เพื่อประมวลผล JSON แค่เรียก jq แล้วป้อน JSON เข้าไปพร้อมกับ JSON path เพื่อระบุว่าเราจะหยิบค่า JSON ตรงไหน แค่นี้เราก็จะได้ผลลัพท์คืนกลับมาอย่างรวมเร็วแล้วล่ะฮะ

jq

เราสามารถติดตั้ง jq ผ่าน homebrew ได้นะ ซึ่งผมได้เขียนบล็อกไปก่อนหน้านี้


Syntax

cat <json_file> | jq '<jq_statement>'
jq '<jq_statement>' <json_file>

คำสั่งแรกคือ ให้อ่าน JSON file แล้วใช้ pipe (|) เพื่อส่งต่อ output ไปยัง jq command ส่วนคำสั่งที่สอง คือการ execute jq โดยตรงไปยัง file โดยไม่ต้องสั่งให้อ่านก่อนฮะ

ทีนี้ ถ้าเราต้องการบันทึกผลลัพท์จาก jq ไปที่ file เดิม สามารถใช้ sponge ได้ฮะ ซึ่ง Sponge เนี่ยมันเป็น package ไว้จัดการเขียน file บน Linux สามารถดาวน์โหลดผ่าน Homebrew ได้ที่ลิงก์นี้เลย

Sponge ใช้ร่วมกันได้แบบนี้ฮะ

cat <json_file> | jq '<jq_statement>' | sponge <json_file>
jq '<jq_statement>' <json_file> | sponge <json_file>

เพียงเท่านี้เราก็จะได้ file ใหม่ที่เรากำหนดตรงคำสั่งหลัง sponge แล้ว


jq applications

ติ๊ต่างว่าเรามี JSON หน้าตาแบบนี้นะ

มันมี jq function หลายอย่างให้เราเลือกใช้ตามสถานการณ์ฮะ เช่น 9 ตัวอย่างต่อไปนี้

1. จิ้มหา key ตรงๆ

cat contents.json | jq '.id'
cat contents.json | jq '.id, .name'
cat contents.json | jq '.id, .name, .items'

สามารถดึงค่าจาก key ได้โดยตรงด้วยเติมจุดไว้ข้างหน้าชื่อ key ซึ่งระบุ key จาก root ฮะ

2. เข้าไปใน array

cat contents.json | jq '.classes[0]'
cat contents.json | jq '.classes[0].town'

ถ้าเป็น array ก็สามารถระบุ index ได้ตามปกติฮะ

3. ใช้ select

cat contents.json | jq '.classes[] | select(.level==50)'
cat contents.json | jq '.classes[] | select(.level==50) | .job'

ฟังก์ชัน select() ใช้กรอง array โดยใส่ boolean expression เพื่อคัดเอา element ที่เราเลือกฮะ

จากตัวอย่าง .classes[] เราใส่ [] เพื่อแปลงจาก array เดี่ยวๆ มาเป็น element set จากนั้นก็ใช้ select (.level==50) เพื่อกรองโดยเทียบ key level ที่มีค่าเท่ากับ 50

4. regex

cat contents.json | jq '.items[] | match("^st.*")'
cat contents.json | jq '.items[] | match("^st.*") | .string'

Regex ก็ใช้ได้นะ ฟังก์ชันที่สามารถใช้กับ regex ได้ ตัวอย่างเช่น match() ซึ่งมันจะคืนค่ามาเป็น object

จากตัวอย่างคือหาคำที่ขึ้นต้น (^) ด้วย st จากนั้นตามด้วยอะไรก็ได้ (.*)

อ้อ มีบล็อกที่เคยเขียนเรื่อง regex ตรงนี้ฮะ ถ้าสนใจอยากอ่านเพิ่มเติมนะฮะ

5. regex ควบ select

cat contents.json | jq '.classes[] | select(.job|match("ma"))'

ทีนี้มาลองใช้ select() คู่กับ match() เพื่อกรอง key ที่อยู่ใน array of object

เริ่มจากเราใช้ [] เพื่อแงะ array ออกแล้วใช้ select() โดยป้อน key และ regex เช่น function match() คั่นด้วย pipe | ฮะ

จากตัวอย่างคือ เลือกจาก .classes หาตัวที่มี job มีคำว่า ma อยู่ข้างใน ก็มีทั้งคู่เลยฮะ ทั้ง "swordman" และ "mage"

6. แปลงค่า

cat contents.json | jq '.cash | map(.amount |= .+200)'

รอบนี้เราจะใช้ map มันจะใช้คู่กับ iterator ฮะ ตัวอย่างของ iterator คือ array นั่นแหละ

จากตัวอย่าง เราเลือก cash ซึ่งเป็น array นะ แต่เราไม่เติม [] เพราะเราต้องการ iterator ถ้าเติม [] มันจะกลายเป็น element แยกกัน จะไม่ใช่ iterator  ทันที จากนั้นก็ใช้ map แล้วเลือกให้  key amount update (|=) ตัวเองให้มีค่าเท่ากับตัวเอง (.) เพิ่มไปอีก 200

map จะคืนค่ากลับมาเป็น array นะฮะ

7. แปลงค่าแล้วเลือก key

cat contents.json | jq '.cash | map(.amount |= .+200) | map(.)[]'
cat contents.json | jq '.cash | map(.amount |= .+200) | map(.)[] | .id,.amount'

เพราะ map มันคืนค่าเป็น array แบบที่ว่าไปข้างบน เวลาจะเลือก key ก็แค่ใช้ map อีกรอบแล้วแกะตัวมันเอง (.) ออกมาเป็น element set ([]) เท่านี้ก็พร้อมให้เราจิ้มเลือก key ที่ต้องการได้แล้วล่ะฮะ

8. แปลงค่าแล้วสร้าง JSON ใหม่

cat contents.json | jq '.cash | map(.amount |= .+200) | map(.)[] | {id:.id, amount:.amount}' | jq -s

เราสามารถรวมผลลัพท์มาเป็น object ใหม่แค่ใช้ {...}

จากนั้นก็รวบตึงทั้งหมด ด้วยคำสั่ง jq -s ตรง -s เป็น "slurp" flag ที่หมายถึง "read entire input into a large array" นั่นเองฮะ

9. เขียนลง file ด้วย Sponge

sponge package ที่คุยไว้ข้างบนนู่น ตรงหัวข้อ "Syntax" สามารถใช้ตรงนี้ได้ฮะ

cat contents.json | jq '...' | sponge contents.json

อ่าน file แล้วประมวลผลด้วย jq ก่อนจะใช้ sponge เพื่อเขียนผลลัพท์กลับไปที่ file เดิม

ง่ายมากๆ เลยฮะ


อ้างอิง