Built-in operators and functions

الاضافة +

يقوم هذا المعامل بتطبيق عدة فلاتر ثم جمع النتائج معا

  • اذا كانت النتائج أرقام: يتم جمع الارقام

  • اذا كانت النتائج حروف يتم دمجهم الي سلسلة حروف اكبر

  • اذا كانت النتائج مصفوفات يتم دمجهم في مصفوفة كبيرة

  • اذا كانت النتائج Objects يتم عمل merge لهم, ولعمل deep merge استخدم المعامل *

  • اذا كان أحد النتائج null لا يتم تغيير شئ

$ echo '{"a": 7}' | jq '.a + 1'
8

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

$ echo '{"a": 1}' | jq '.a + null'
1

$ echo '{}' | jq '.a + 1'
1 # null + 1 = 1

$ echo null | jq '{a: 1} + {b: 2} + {c: 3} + {a: 42}'
{
  "a": 42,
  "b": 2,
  "c": 3
}

الطرح -

يشبه عملية الاضافة وللكن بالعكس

في حالة المصفوفات يتم حذف العناصر بدلا من اضافتها

$ echo '{"a":3}' | jq '4 - .a'
1

$ echo '["xml", "yaml", "json"]' | jq '. - ["xml", "yaml"]'
[
  "json"
]

عمليات الضرب والقسمة

  • بالنسبة للارقام يتم اجراء العمليات الحسابية العادية

  • عند ضرب string في رقم يتم تكرار الـ string هذا العدد من المرات

  • أما قسمة string علي string اخر يؤدي الي تقسيم ال string الاصلي الي أجزاء عند مكان الـ string الاخر

// dividing number
// `10 / 5 * 3` = 6
$ echo 5 | jq '10 / . * 3'
6

$ echo "a, b,c,d, e" | jq  '. / ", "'
jq: parse error: Invalid numeric literal at line 1, column 2
[ble: exit 5]

// split strings at ", "
$ echo '"a, b,c,d, e"' | jq  '. / ", "'
[
  "a",
  "b,c,d",
  "e"
]

// deep merging objects
// for shallow meging use `+`
$ echo null | jq '{"k": {"a": 1, "b": 2}} * {"k": {"a": 0,"c": 3}}'
{
  "k": {
    "a": 0,
    "b": 2,
    "c": 3
  }
}

// divide numbers
// note: we use `?` here to avoid error when dividing by zero
$ echo [1,0,-1] | jq '.[] | (1 / .)?'
1
-1

$ echo [1,0,-1] | jq '.[] | (1 / .)'
1
jq: error (at :1): number (1) and number (0) cannot be divided because the divisor is zero

القيمة المطلقة abs

وهو يستخدم لايجاد القيمة المطلقة (الموجبة) للرقم

$ echo 1 | jq abs
1

$ echo -1 | jq abs
1

$ echo 0 | jq abs
0

$ echo -1e-1 | jq abs
0.1

الطول length

  • بالنسبة للـ strings يكون عدد حروف النص

  • بالنسبة للأرقام يعطي القيمة الموجبة (مثل abs)

  • بالنسبة للمصفوفات والـ objects يكون عدد عناصر المصفوفة

  • طول null هو صفر

$ echo -1 | jq length
1

$ echo "hello" | jq length
jq: parse error: Invalid numeric literal at line 2, column 0
[ble: exit 5]

$ echo '"hello"' | jq length
5

$ echo '[1, 2, 3]' | jq length
3

$ echo '{"a": 1, "b": 2}' | jq length
2

$ echo null | jq length
0

$ echo true | jq length
jq: error (at :1): boolean (true) has no length

بالنسبة لـ UTF8 unicode strings يمكن استخدام الدالة utf8bytelength

$ echo '"😉"' | jq length
1

$ echo '"😉"' | jq utf8bytelength
4

$ echo '"\u03bc"' | jq utf8bytelength
2

الدالة keys

تعود هذه الدالة بمفاتيح الـ object مرتبة تصاعديا, وللحصول علي المفاتيح بدون ترتيب يمكن استخدام keys_unsorted

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

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

$ echo '[1, 2, 3]' | jq keys
[0, 1,  2]

ويمكن معرفة اذا كان العنصر يحتوي علي مفتاح ما باستخدام has(key)

$ echo '{"a": 1, "c": 2, "b": 3}' | jq 'has("a")'
true

$ echo '{"a": 1, "c": 2, "b": 3}' | jq 'has("x")'
false

أو باستحدام in

$ echo '"a"' | jq 'in({"a": 1})'
true

$ echo '"b"' | jq 'in({"a": 1})'
false

$ echo '1' | jq 'in([1, 2, 3])'
true

$ echo '5' | jq 'in([1, 2, 3])'
false

اختيار عناصر باستخدام pick

$ echo '{"a": 1, "b": {"c": 2, "d": 3}, "e": 4}' | jq 'pick(.a, .b.c, .x)'
{
  "a": 1,
  "b": {
    "c": 2
  },
  "x": null
}

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

$ echo [1,2,3,4] | jq 'pick(.[0])'
[1]

$ echo [1,2,3,4] | jq 'pick(.[1])'
[null, 2]

$ echo [1,2,3,4] | jq 'pick(.[2])'
[null, null, 3]

$ echo [1,2,3,4] | jq 'pick(.[0, 1])'
[1, 2]
// pick(.[2]) -> [null, null, 3]
// pick(.[0]) -> [1]
// all together: [null, null, 3] + [1] + [1] = [1, null, 3]
// this gives the same result as `pick(.[0], .[2], .[0])`

$ echo [1,2,3,4] | jq 'pick(.[2], .[0], .[0])'
[1, null, 3]

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

النتيجة تكون array كل عنصر فيها يحدد segment من المسار

$ echo '{"a": [{"b": 1}]}' | jq 'path(.a)'
["a"]

$ echo '{"a": [{"b": 1}]}' | jq 'path(.a[0])'
["a", 0]

$ echo '{"a": [{"b": 1}]}' | jq 'path(.a[0].b)'
["a", 0, "b"]

$ echo '{"a": [{"b": 1}]}' | jq 'path(.)'
[]

// reach the most deep element using the recursive descent operator `..`
$ echo '{"a": [{"b": 1}]}' | jq 'path(..)'
[]
["a"]
["a", 0]
["a", 0, "b"]

لنتدبر قليلا المثال الأخير حتي نفهم العملية بعمق أكبر

المعامل .. يستخدم للوصول الي اعمل عنصر داحل المصفوفات والـ objects وبالتالي path(..) يعني الحصول علي مسار أعمل عنصر في الـ input

وهو يقوم بارجاع مصفوفة كل عنصر فيها يمثل segment من المسار

الـ segment الاول [] هو مسار الـ root

ال segement الثاني هو مسار العنصر “a'“

الـ segment الثالث [“a”, 0] هو مسار العنصر الاول في المصفوفة داخل العنصر “a”

الـ segment الرابع [“a”, 0, “b”] هو مسار العنصر “b” داخل العنصر الاول في المصفوفة داخل العنصر “a”

فكر فيها كأنها شجرة

(root)
   └── a
       └── 0
            └── b

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

أو تحديد قيمة عنصر في مسار معين باستخدام setpath(path; value)

ويمكن حذف عنصر عند مسار معين باستخدام delpaths

$ echo '{"report" : {"test": "ok"}}' | jq 'getpath(["report"])'
{"test": "ok"}

$ echo '{"report" : {"test": "ok"}}' | jq 'getpath(["report", "test"])'
"ok"

$ echo '{"a":{"b":0, "c":1}}' | jq '[getpath(["a","b"], ["a","c"])]'
[0, 1]

// set path using `setpath(path; value)`
$ echo '{"report" : {"test": "ok"}}' | jq 'setpath(["report", "test"]; "failed")'
{
  "report": {
    "test": "failed"
  }
}


$ echo '{"report" : {"test": "ok"}}' | jq 'setpath(["report", "test"]; "failed") | getpath(["report", "test"])'
"failed"

$ echo null | jq 'setpath(["a","b"]; 1)'
{
  "a": {
    "b": 1
  }
}

$ echo null | jq 'setpath([0,"a"]; 1)'
[
  {
    "a": 1
  }
]

// delete path
$ echo '{"a":{"b":1},"x":{"y":2}}' | jq 'delpaths([["a","b"]])'
{
  "a": {},
  "x": {
    "y": 2
  }
}

ويمكن استخدام paths للحصول علي مسارات جميع عناصر الـ input

$ echo '[1,[[],{"a":2}]]' | jq paths
[[0],[1],[1,0],[1,1],[1,1,"a"]]

// paths of number values only
$ echo '[1,[[],{"a":2}], null]' | jq 'paths(type == "number")'
[[0],[1,1,"a"]]

التحويل من object الي مصفوفة او العكس

يمكن استخدام الدوال to_entries, from_entries, with_entries لتحويل مصففوفة الي object او العكس

بالنسبة لـ with_entries(filter) فهي اهتصار لـ to_entries | map(filter) | from_entries أي تحويل الـ object الي مصفوفة من key-value pairs وتطبيق الفلتر علي كل عنصر من عناصرها ثم اعادتها لـ object جديد

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

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

$ echo '{"a": 1, "b": 2}' | jq 'with_entries(.key |= "KEY_" + .)'
{
  "KEY_a": 1,
  "KEY_b": 2
}

لنأخذ المثال الأخير هطوة خطوة, كما ذكرنا فان with_entries تقوم بتحويل الـ object الي مصفوفة ثم تطبيق الفلتر علي كل عنصر من عناصر هذه المصفوفة ثم اعادتها لـ object مرة اخري

الفلتر المستخدم هنا هو .key |= "KEY_" + . وهو يعني:

  • .key → مفتاح العنصر الحالي

  • |= → modify

  • ”KEY_” + . → add KEY_ to the current value

// step 1: apply `to_entries`
$ echo '{"a": 1, "b": 2}' | jq  to_entries
[
  {"key": "a", "value": 1 },
  {"key": "b", "value": 2}
]

// step 2: apply the filter to each item of the array (without the modification part)
$ echo '[{"key": "a", "value": 1 }, {"key": "b", "value": 2}]' | jq 'map(.key | .)'
["a", "b"]

// let's add another part to the filter `|=`
$ echo '[{"key": "a", "value": 1 }, {"key": "b", "value": 2}]' | jq 'map(.key |= .)'
[
  {
    "key": "a",
    "value": 1
  },
  {
    "key": "b",
    "value": 2
  }
]


// now let's see the full filter effect
$ echo '[{"key": "a", "value": 1 }, {"key": "b", "value": 2}]' | jq 'map(.key |= "KEY_" + .)'
[
  {"key": "KEY_a", "value": 1},
  {"key": "KEY_b", "value": 2}
]

// step 3: convert the array back into an object using `from_entries`
$ echo '[{"key": "KEY_a", "value": 1}, {"key": "KEY_b", "value": 2}]' | jq from_entries
{
  "KEY_a": 1,
  "KEY_b": 2
}

القيمة الفارغة

يمكن ارجاع قيمة فارغة تماما (ولا حتي null) باستخدام empty

$ echo null |  jq 'empty'

$ echo null |  jq '1, empty, 2'
1
2

ويمكن استخدام هذه الميزة مثلا في الفلترة (بديل لـ select)

لنستعير أحد الأمثلة التي استخدمنا فيها select ونستخدم empty بدلا منها

$ echo '[1,5,3,0,7]' | jq '.[] | select(. > 2)'
5
3
7

$ echo '[1,5,3,0,7]' | jq '.[] | if . > 2 then . else empty end'
5
3
7

// remove elements from an array (using `map()` or `[...]`)
$ echo '[1,5,3,0,7]' |  jq 'map(if . > 2 then . else empty end)'
[5, 3, 7]

// remove null values
$ echo '[1,null,2]' | jq '.[] | if . == null then empty else . end'

$ echo null | jq '. // empty'

$ echo true | jq '. // empty'
true

المعامل // يعني القيمة الافتراضية اذا كانت القيمة الاصلية falsy ويمكن الاستفادة من هذه الحركة في حذف العناصر التي تعطي قيمة falsy

$ echo '[1, true, false, null, "ok"]' | jq 'map(. // empty)'
[1, true, "ok"]

// to demonstrate the operator `//` better:
$ echo '[1, true, false, null, "ok"]' | jq 'map(. // "falsy value")'
[
  1,
  true,
  "falsy value",
  "falsy value",
  "ok"
]

التعامل مع الاخطاء

يمكن رمي Exception باستخدام الدالة error ويمكن تمرير رسالة اليها error(message)

الرسالة الافتراضية هي قيمة العنصر الحالي

ويمكن مسك الخطأ باستخدام try & catch

وللحصول علي اسم الملف ورقم السطر الذي حدث فيه الايرور يمكن استخدام $__loc__

$ echo '"something wrong"' | jq 'error'
jq: error (at :1): something wrong

$ echo '"something wrong"' | jq 'error("custom message")'
jq: error (at :1): custom message

$ echo '"something wrong"' | jq 'try error("custom message") catch "oops"'
"oops"

$ echo '"something wrong"' | jq 'try error("custom message") catch .'
"custom message"

$ echo '"something wrong"' | jq 'try error catch .'
"something wrong"

$ echo null | jq 'try error("\($__loc__)") catch .'
"{\"file\":\"\",\"line\":1}"

ويمكن ايقاف jq بدون أي نتائج مع exit code 0 باستخدام halt او مع خطأ باستخدام halt_error أو halt_error(exit_code = 5)

// exit code: 0
$ echo null | jq 'halt'

// exit code: 5 (the default)
$ echo null | jq 'halt_error'
exit 5

// exit code: 1
$ echo null | jq 'halt_error(1)'
exit 1

التعامل مع الارقام

يمكن توليد أرقام باستخدام range

// from 0 up to 3
$ echo null | jq 'range(3)'
0
1
2

// from 3 up to 5
$ echo null | jq 'range(3; 5)'
3
4

// from 1 up to 10, every 2 steps
$ echo null | jq 'range(1; 10; 2)'
1
3
5
7
9

// inverse counting
$ echo null | jq 'range(1; -5; -1)'
1
0
-1
-2
-3
-4

يتم تقريب الارقام باستخدام floor

$ echo 3.14159 | jq floor
3

ولحساب الجزر التربيعي لرقم استخدم sqrt

$ echo 9 | jq sqrt
3

تحويل الأنواع

يمكن استخدام دوال to* مثل tonumber و tostring لتحويل الأنواع

ويمكن التحقق من نوع عنصر باستخدام الدوال is* مثل isinfinite

وكذلك يمكن استخدام الدالة type للحصول علي نوع العنصر

$ echo '"1"' | jq tonumber
1

$ echo '"test"' | jq type
"string"

$ echo 1 | jq isfinite
true

تطبيق فلتر بشكل متكرر علي المدخلات

يمكن استخدام الدالة while لتطبيق فلتر معين بشكل متكرر, هذه الدالة تأخذ شرط, ويتم تكرار الفلتر حتي يصبح الشرط false

مثلا لأخذ رقم وضربه في 2 طالما الناتج أقل من 100

$ echo 1 | jq '[while(.<100; .*2)]'
[1,2,4,8,16,32,64]

أو باستخدام until حيث يتم التكرار حتي يتحقق الشرط

$ echo 1 | jq '[until(.>100; .*2)]' -c
[128]

أو يمكن استخدام repeate للتكرار حتي حدوث خطأ

$ echo 1 | jq '[repeat(.*2, error)?]'
[2]

ويمكن استخدام recurse لاستخراج داتا من كل المستويات

مثلا لو لدينا نظام ملفات بهذا الشكل

{
  "name": "/",
  "children": [
    {
      "name": "/bin",
      "children": [
        {
          "name": "/bin/ls",
          "children": []
        },
        {
          "name": "/bin/sh",
          "children": []
        }
      ]
    },
    {
      "name": "/home",
      "children": [
        {
          "name": "/home/stephen",
          "children": [
            {
              "name": "/home/stephen/jq",
              "children": []
            }
          ]
        }
      ]
    }
  ]
}

ونريد استخراج أسماء جميع الملفات, في العادي سنحتاج لعمل شئ مثل:

.name
.children[].name
.children[].children[].name
...

أو يمكن ببساطة استخدام recurse لهذا الغرض

$ echo '{
  "name": "/",
  "children": [
    {
      "name": "/bin",
      "children": [
        {
          "name": "/bin/ls",
          "children": []
        },
        {
          "name": "/bin/sh",
          "children": []
        }
      ]
    },
    {
      "name": "/home",
      "children": [
        {
          "name": "/home/stephen",
          "children": [
            {
              "name": "/home/stephen/jq",
              "children": []
            }
          ]
        }
      ]
    }
  ]
}' | jq 'recurse(.children[]) | .name'
"/"
"/bin"
"/bin/ls"
"/bin/sh"
"/home"
"/home/stephen"
"/home/stephen/jq"

قمنا أولا بتطبيق الفلتر recurse(.children[]) وهو يقوم باستخراج children لجميع المستويات, ثم تطبيق فلتر .name علي كل نتيجة لاستخراج اسم الملف

ويمكن تمرير شرط لـ recurse() لتنفيذ العملية طالما ان الشرط متحقق

echo 1 |  jq 'recurse(. *2; . < 5)'
1
2
4

مثال اخر لاستخراج كل العناصر في جميع المستويات

$ echo '{"a":0,"b":[1]}' | jq recurse
{"a": 0, "b": [1]} //<-- level 1: the object itself
0 //<-- first item of level 2: `"a": 0`
[1] //<-- second item of level 2: `"b": [1]`
1 //<-- level 3

أما الدالة walk تقوم بتطبيق فلتر معين علي كل العناصر, وفي حالة وجود عنصر عبارة عن مصفوفة تقوم أولا بتطبيق الفلتر علي كل عناصر المصفوفة ثم علي المثفوفة نفسها, وفي حالة Object يتم تطبيق الفلتر علي كل الـ values ثم علي الـ object نفسه
مثلا لترتيب عناصر كل مصفوفة:

$ echo '[[4, 1, 7], [8, 5, 2], [3, 6, 9]]' | jq 'walk(if type == "array" then sort else . end)'
[[1,4,7],[2,5,8],[3,6,9]]

في المثال السابق, تم استخدام walk لتطبيق الفلتر علي كل عناصر كل مصفوففة ثم علي المصفوفة نفسها

الفلتر نفسه يقول, اذا كان العنصر Array قم بتطبيق sork والذي يقوم بترتيب عناصر المصفوفةو غير ذلك قم بتطبيق . أي اترك العنصر كما هو.

مثال اخر علي حالة object, وفي هذا المثال نريد ازالة “_” في بداية اسم كل مفتاح, هيا نفكر سويا في الحل
بفرض أن لدينا object كالتالي ونريد حذف “_” من بداية اسم أي مفتاح في هذا الـ object

[ { "_a": { "__b": 2 } } ]

طبعا سيتم حذف هذا الرمز باستخدام sub والذي يقوم باستبدال حروف باخري, ولاننا نريد “_” في بداية الكلمة, اذا سنستخدم هذا ال RegExp ^_+ وهو يعني:

  • ^ → at the beginning

  • + any number of it

لتطبيق هذا الفلتر علي عناصر object سنستخدم with_entries فنقول

with_entries(.key |= sub("^_+"; ""))

استخدمنا .key للحصول علي مفاتيح الـ object, والعلامة |= تعني اجراء تعديل عليه

الان نريد تطبيق ذلك علي جميع الـ objects والـ objects الفرعية في جميع المستويات فنستخدم walk وتكون النتيجة النهائية:

$ echo '[ { "_a": { "__b": 2 } } ]' | jq 'walk( if type == "object" then with_entries( .key |= sub( "^_+"; "") ) else . end )'
[{"a":{"b":2}}]

التعامل مع النصوص

العمليات علي المصفوفات