jq - แค่ใช้ Bash ก็ประมวล JSON ได้
JSON เป็น file format ที่เจอกันบ่อยมากเลยฮะ ผมเคยเขียนถึงไปแล้วในบล็อกนี้ สำหรับการจัดการ file นี้มีหลายวิธีมากๆ สำหรับบล็อกนี้เลยขอเล่าถึงวิธีบ้านๆ แต่ง่ายและมีประสิทธิภาพดีเลยฮะ แค่เราใช้ command line ด้วย package "jq" ฮะ
ถ้าสนใจ Bash script และ command line สามารถอ่านต่อได้ที่บล็อกนี้ได้เลยนะฮะ
jq คืออะไร
jq
เป็น package ใช้ใน command line เพื่อประมวลผล JSON แค่เรียก jq
แล้วป้อน JSON เข้าไปพร้อมกับ JSON path เพื่อระบุว่าเราจะหยิบค่า JSON ตรงไหน แค่นี้เราก็จะได้ผลลัพท์คืนกลับมาอย่างรวมเร็วแล้วล่ะฮะ
เราสามารถติดตั้ง 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 เดิม
ง่ายมากๆ เลยฮะ