jq filters

فلاتر jq

الفلاتر هي العمليات التي يمكن اجراءها علي محتوي JSON الخاص بنا, مثل تعديل أو حذف او اضافة حقل أو اي عمليات أخري كما سنتعلم الان

الفلتر Identity

وهو أبسط فلتر يمكن استخدامه حيث لا يفعل شئ .. حرفيا

هذا الفلتر يأخد الـ input ويقوم باخراجه منا هو دون أي تعديل

$ echo '{"test": "ok"}' | jq .
{
  "test": "ok"
}

رغم أنه يبدو فتر تافها, ولكن له بعض الاستخدامات مثل اخراج محتوي JSON منسق كما تري في نتيجة تنفيذ الأمر السابق

الفلتر Object index

يستخدم هذا الفلتر للوصول الي مفاتيح الـ object واجراء بعض العمليات عليها

ويتم ذلك باستخدام اسم المفتاح كالتالي .key أو للمفاتيح المتداخلة .key.nested

ويمكن ايضا كتابة المفتاح كالتالي .[“key”] بدلا من .key وهو مفيد اذا كان اسم المفتاح يحتوي علي رموز خاصة أو يبدأ برقم

$ echo '{"test": { "result": "ok" }}' | jq .test
{
  "result": "ok"
}

# a key can be written as `.["foo"]` instead of `.foo`
$ echo '{"test": { "result": "ok" }}' | jq '.["foo"]'
{
  "result": "ok"
}

# nested elements
$ echo '{"test": { "result": "ok" }}' | jq .test.result
"ok"

# similar to `.test.result`
$ echo '{"test": { "result": "ok" }}' | jq '.test | .result'
"ok"

# non-existing key
# or better `.test.missing?`
$ echo '{"test": { "result": "ok" }}' | jq .test.missing
null

# array access
$ echo '["green", "red", "yellow"]' | jq '.[0]'
"green"

# inversing counting
$ echo '["green", "red", "yellow"]' | jq '.[-1]'
"yellow"

$ echo '["green", "red", "yellow"]' | jq '.[3]'
null

# slicing
$ echo '["green", "red", "yellow"]' | jq '.[0:2]'
[
  "green",
  "red"
]

# from the first item unil the pre-last one
$ echo '["green", "red", "yellow"]' | jq '.[0:-1]'
[
  "green",
  "red"
]

# slicing also works with strings
$ echo '"welcome"' | jq '.[0:2]'
"we"

# get all items
$ echo '["green", "red", "yellow"]' | jq '.[]'
"green"
"red"
"yellow"

# get selected items
$ echo '["green", "red", "yellow"]' | jq '.[0,2]'
"green"
"yellow"


$ echo '{"colors": ["green", "red", "yellow"] }' | jq '.colors'
[
  "green",
  "red",
  "yellow"
]

# equivalent to `.colors | []`
# for optional index use `.colors[]?`
$ echo '{"colors": ["green", "red", "yellow"] }' | jq '.colors[]'
"green"
"red"
"yellow"


# get selected keys
$ echo '{"country": "Egypt", "capital": "Cairo", "contenient": "Africa"}' | jq '.country, .capital'
"Egypt"
"Cairo"

اذا كان الحقل المطلوب التعامل معه اختياري, أي يمكن أن يكون غير موجود يفضل استخدام هذه الصيغة .key?

لاحظ أن .colors يقوم بارجاع النتيجة علي هيئة Array أما .colors[] يقوم بارجاع كل عنصر بشكل مستقل

عند استخدام فاصلة بين فلترين كما في .country, .capital فانه يقوم بادخال نفس الـ input علي كل فلتر فيهما ثم يقوم بجمع المخرجات بنفس الترتيب

فمثلا .country تقوم بارجاع “Egypt” و .capital تقوم بارجاع “Cairo”, وبالتالي فان القيمة النهائية هي جمع المخرجات معا فتكون “Egypt” و “Cairo”

بنفس المبدأ فان .[0,2] كما في المثال السابق يقم بارجاع نتيجة الفلتر .[0] وكذلك الفلتر .[2] ويقوم بجمعهما معا لتكون النتيجة النهائية "green" و "yellow"

أنابيب الفلاتر Pipes

يمكن انشاء الفلاتر علي هيئة أنابيب (pipes), بمعني أن مخرج كل فلتر يصبح مدخل الفلتر التالي له وذلك باستخدام |

أذا كنت تستخدم أوامر لينكس فبالتأكيد أنت شاهدت كثيرا المعامل | بين العمليات وبعضها.

نحن استخدمناه بالفعل في أمثلتنا السابقة joy emoji هل لاحظت عندما نقول echo … | jq … فانت هنا تقول خذ نتائج echo وارسلها الي jq

أي فلتر يقوم باخراج عدة نتائج فانه كل نتيجة منهم ستصبح مدخل للفلتر التالي له

وبالتأكيد نحن رأينا في الامثلة السابقة أن key1.key2 هي اختصار لعملية .key1 | .key2

فكر قليلا في المثال التالي قبل مشاهدة الحل

[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]

نريد معرفة اسم كل عنصر, اي استخراج الحقل name من كل عنصر من عناصر المجموعة

كما نري فان كل عنصر عبارة عن Object يحتوي علي name وبعض الحقول الاخري

نريد أولا الحصول علي كل عنصر علي حدي, لذا سنستخدم الفلتر .[] وهذا سيعطينا عنصرين كل واحد منهم عبارة عن Object, نريد ادخال هذه النتائج الي الفلتر التالي علي هيئة inputs ليقوم باستخراج اسم كل عنصر

سنتحناج بعد ذلك للحصول علي الحقل name من كل عنصر لذا سنتستخدم الفلتر .name

وبذلك يكون الحل النهائي هو .[] | .name

بما أن .[] يقوم باخراج عدة نتائج فان كل نتيجة تذخب بشكل منفصل الي الفلتر التالي وهو .name

# step 1: extract each item using `.[]`
$ echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[]'
{
  "name": "JSON",
  "good": true
}
{
  "name": "XML",
  "good": false
}

# step2: extract item name using `.name`
$ echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[] | .name'
"JSON"
"XML"

# same result
$ echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[].name'
"JSON"
"XML"

الأقواس

الأقواس تستخدم لجمع العمليات في مجموعات

$ echo 1 | jq '(. + 2) * 5'
15

# changing parenthesis order
$ echo 1 | jq '. + (2 * 5)'
11

يتم أولا تنفيذ (. + 2) ثم ضرب الناتج في 5

الفلتر . يقوم بارجاع المدخل كما هو ثم جمع 2 عليه تصبح انتيجة 3 ثم ضربها في 5 تصبح 15

اختبار سريع

ما نتيجة تنفيذ الأمر التالي؟

echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq '[.user, .projects[]]'

لنفكر في الأمر خطوة خطوة

أولا يتم تنفيذ الفلتر .user وهو يقوم بارجاع القيمة “stedolan”

وكذلك يتم تنفيذ الفلتر .projects[] ولكن فكر قليلا ما هو الـ input الداخل لهذا الفلتر؟ اذا قولت “stedolan” فانت تحتاج لاعادة التفكير

هل تتذكر حينما قلنا أن الفاصلة تعني ادخال نفس الـ input علي جميع الفلاتر ثم جمع النتائج, وبالتالي فان المدخل هنا هو المحتوي الاصلي نفسه, وبتطبيق .projects[] يتم ارجاع القيم "jq" و "wikiflow"

تذكر أن هذه العملية تختلف عن .projects التي تقوم بارجاع [ "jq", "wikiflow"] علي هيئة Array

أما .projects[] تقوم بارجاع كل عنصر من عناصر المصفوفة علي حدي

الان يتم وضع النتائج النهائية داخل [] وهو يعني جمع النتائج كلها علي هيئة مصفوفة

هيا نري الخطوات خطوة خطوة

# extract the user
$ echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq .user
"stedolan"

# extract the projects as separate items
$ echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq .projects[]
"jq"
"wikiflow"

# apply multiple filters `.user, .projects[]`
$ echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq '.user, .projects[]'
"stedolan"
"jq"
"wikiflow"

# combine all results as an Array
$ echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq '[.user, .projects[]]'
[
  "stedolan",
  "jq",
  "wikiflow"
]

بنفس طريقة التفكير, فكر في نتائج تنفيذ الأمر التالي

echo '[1, 2, 3]' | jq '[ .[] | . * 2]'

لنفكر سويا thinking emoji

أولا يتم تطبيق الفلتر .[] وهو يقوم بارجاع النتائج كل نتيجة بشكل مستقل

ثم يتم عمل pipe باستخدام | وهو يعني أن كل نتيجة سيتم استخدامها كـ input للفلتر التالي

الفلتر . يقوم باراه الـ input كما هو, ثم يتم ضربه في 2

وفي الاخر يتم تجميع المخرجات علي هيئة Array

$ echo '[1, 2, 3]' | jq '[ .[] | . * 2]'
[
  2,
  4,
  6
]

فلتر Object Construction

يستخدم هذا الفلتر لانشاء Object جديد

نريد انشاء Object كالتالي { firstProject: ?? } بحيث تكون قيمة firstProject هي قيمة أول project في الـ JSON التالي

{"user":"stedolan", "projects": ["jq", "wikiflow"]}

كالعادة, هيا نفكر في الحل خطوة خطوة

أولا نقوم باستخراج القيمة المطلوبة, وهي أول عنصر في projects باستخدام الفلتر .projects[0]

الان نقوم بانشاء الـ object باستخدام هذه القيمة باستخدام الفلتر {} والذي يقوم باشاء Object جديد

$ echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq '{firstProject: .projects[0]}'
{
  "firstProject": "jq"
}

يمكن ايضا استخدام هذا الفلتر لاختيار عناصر معينة من object

$ echo '{"id": 1, "name": "meao", "type": "cat"}' | jq '{name: .name, type: .type}'
{
  "name": "meao",
  "type": "cat"
}

وهنا يوجد اختصار لهذا الامر أن نستخدم {name, type} دون الحاجة لتحديد القيم

$ echo '{"id": 1, "name": "meao", "type": "cat"}' | jq '{name, type}'
{
  "name": "meao",
  "type": "cat"
}


$ echo '{"id": 1, "name": "meao", "type": "cat"}' | jq '{name, number: .id}'
{
  "name": "meao",
  "number": 1
}

فكر قليلا في نتيجة تنفيذ الأمر التالي, لا تتسرع بالاجابة لأنه يوجد trick بسيطة

echo '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}' | jq '{user, title: .titles[]}'

اذا كانت اجابتك:

{
  "user": "stedolan",
  "title": [
    "JQ Primer",
    "More JQ"
  ]
}

فانت تحتاج لاعادة التفكير face_with_peeking_eye emoji

تذكر أن .titles[] يقوم بارجاع كل عنصر بشكل مستقل وبالتالي النتيجة ستكون متعددة العناصر

$ echo '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}' | jq '{user, title: .titles[]}'
{
  "user": "stedolan",
  "title": "JQ Primer"
}
{
  "user": "stedolan",
  "title": "More JQ"
}

يمكن أيضا استخدام أي قيمة لتصبح هي اسم المفتاح, مثلا لجل اسم المستخدم هو نفسه اسم المفتاح, وبالتالي يمكن انتاج نتيجة بهذا الشكل {stedolan: …}

$ echo '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}' | jq '{(.user): .titles}'
{
  "stedolan": [
    "JQ Primer",
    "More JQ"
  ]
}

وضع أقواس علي اسم المفتاح يعني استخدام expression بدلا من نص عادي

ما نتيجة تنفيذ

$ echo '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}' | jq '{(.user): .titles[]}'

لا تنخدع هذه المرة وركز جيدا في .titles[]

الفلتر Recursive Descent

يستخدم للنزول بشكل متكرر داخل العناصر المتاخلة حتي نصل الي العنصر الداخلي

$ echo '[[{"a":1, "b": 2}]]' | jq '..'
[
  [
    {
      "a": 1,
      "b": 2
    }
  ]
]
[
  {
    "a": 1,
    "b": 2
  }
]
{
  "a": 1,
  "b": 2
}
1
2

$ echo '[[{"a":1, "b": 2}]]' | jq '.. | .a?'
1

$ echo '[[{"a":1, "b": 2}]]' | jq '.. | .b?'
2

$ echo '[[{"a":1, "b": 2}]]' | jq '.. | .c?'
null

Built-in operators and functions