JSON İşleme Aracı: jq
Şimdi JSON Düşünsün!
JSON
dosya formatı artık hayatımızın vazgeçilmezi. Gündelik hayatta hep
onunla işimiz oluyor. Peki bir araç olsa, istediğimiz gibi filtreleme
yapsak, hem de hiç kod yazmasak?
Sizi harika bir araçla tanıştırmak istiyorum: jq
. jq
süper hızlı çalışan
hafif siklet bir komut satırı aracı. Tüm platformlarda var. macOS kullanıcıları
hemen:
$ brew install jq
ile kurabilir. Elle kurmak için sitesinden indirip kurulum yapabilirsiniz. Eğer kurulum yapmak istemezseniz, web tarayıcısı üzerinden de kullanabilirsiniz. Dokümantasyonu ve nasıl kullanılacağına dair bir tuturial’i de bulunuyor.
Hemen ufak ufak girişelim.
$ echo '{"x": 1, "y": 2}' | jq
{
"x": 1,
"y": 2
}
$ echo '{"x": 1, "y": 2}' | jq .x
1
Elimizde {key: value}
şeklinde bulunan nesnelerde .key
mantığıyla veriye
ulaşabiliyoruz. Eğer iç-içe yani nested key’ler varsa; .key.key.key
şeklinde
zincirleme ilerleyebiliyoruz. Bu durum Object Identifier-Index olarak geçer:
$ echo '{"x": {"a": "bu a değeri"}}' | jq .x.a
"bu a değeri"
Eğer key var mı? diye bakmak istersek Optional Object Identifier-Index kullanırız:
$ echo '{"foo": 1, "bar": 2}' | jq '.baz?'
null
Yani .key?
şeklinde kullanılır. Eğer key yoksa sonuç null
döner. Genelde
elimizde hep bir liste yani Array ve içinde de objeler olur:
$ echo '[{"id": 1}, {"id": 2}]' | jq .
[
{
"id": 1
},
{
"id": 2
}
]
Eğer flat-map yapmak istersek, yani aradan virgülü atmak;
$ echo '[{"id": 1}, {"id": 2}]' | jq .[]
{
"id": 1
}
{
"id": 2
}
Sadede id
’lerin değerlerini alalım:
$ echo '[{"id": 1}, {"id": 2}]' | jq '.[] | .id'
1
2
Yani jq '.[] | .id'
şeklinde. Elimizde aşağıdaki gibi bir json
olsun:
{
"user": {
"id": 1,
"name": "vigo"
},
"items": [
1,
2,
3
]
}
İçinden sadece .user
’ı alalım:
$ echo '{"user": {"id": 1, "name": "vigo"}, "items": [1,2,3]}' | jq '.user'
{
"id": 1,
"name": "vigo"
}
Aynı nesneden kopya bir liste:
$ echo '{"x": 1}' | jq '[. , . , .]'
[
{
"x": 1
},
{
"x": 1
},
{
"x": 1
}
]
İşin sırrı jq '[. , . , .]'
bu kısında. .
elemanın kendisi oluyor. Elimizde
kullanıcıların listesi var mesela:
[
{
"id": 1,
"name": "vigo"
},
{
"id": 2,
"name": "lego"
},
{
"id": 3,
"name": "figo"
}
]
Sadece .name
’leri alalım:
$ echo '[{"id": 1, "name": "vigo"}, {"id": 2, "name": "lego"}, {"id": 3, "name": "figo"}]' | jq '.[] | .name'
"vigo"
"lego"
"figo"
Map Özelliği
Tüm programlama dillerinde bulunan, iterable yani bir koleksiyonun içinde tüm elemanları bir fonksiyondan geçirme işlemi. Elimizde sayılardan oluşan bir liste var ver biz koleksiyon içindeki her sayıya bir ekleyeceğiz:
[
1,
2,
3,
4,
5
]
$ echo '[1,2,3,4,5]' | jq 'map(. + 1)'
[
2,
3,
4,
5,
6
]
map(. + 1)
-> .
elemanın kendisi, sonrasında +
operatör, 1
de ekleyeceğimiz
değer. .
Identity anlamındadır. Çarpma versiyonu:
$ echo '[1,2,3,4,5]' | jq 'map(. * 2)'
[
2,
4,
6,
8,
10
]
Eğer çıktıların daha kısa ve kompakt olmasını isterseniz -c
parametresini
kullanabiliriniz:
$ echo '[1,2,3,4,5]' | jq 'map(. * 2)' -c
[2,4,6,8,10]
Şimdi elimizdeki aynı listeden bir transformasyon yapalım:
$ echo '[1,2,3,4,5]' | jq 'map({"x": .})'
[
{
"x": 1
},
{
"x": 2
},
{
"x": 3
},
{
"x": 4
},
{
"x": 5
}
]
Fonksiyonlar
En sık kullandığım length
fonksiyonu:
$ echo '[1,2,3,4,5]' | jq 'length'
5
$ jq length /path/to/file.json
Elementin indeksini bulalım. [1,2,3,4,5]
listesinde 2
’nin indeksi 1
’dir
yani 0. element: 1
, 1.element de 2
’dir ya, bunu jq
ile gelen indices
fonksiyonunu kullanarak bulalım:
$ echo '[1,2,3,4,5]' | jq 'indices(2)'
[
1
]
$ echo '[1,2,3,4,5]' | jq 'indices(2) | .[]'
1
String içinde pipe yaparak çıktıyı flat-map yaptık. jq 'komut | .[]'
şeklinde.
Bir kısım fonksiyon örneği verelim:
$ echo '[2, 4, 6, 8]' | jq 'contains([2])' # true
$ echo '[2, 4, 6, 8]' | jq 'reverse' -c # [8,6,4,2]
$ echo '[2, 4, 6, 8]' | jq 'min' # 2
$ echo '[2, 4, 6, 8]' | jq 'max' # 8
$ echo '"hello"' | jq 'split("")' -c # ["h","e","l","l","o"]
$ echo '"hello"' | jq 'test("he*")' -c # true
$ echo '"hello"' | jq 'test("he*!")' -c # false
$ echo '"hello"' | jq 'contains("a")' -c # false
$ echo '"hello"' | jq 'startswith("h")' -c # true
$ echo '"hello"' | jq 'endswith("o")' -c # true
$ echo '[{"user": {"id": 1, "name": "vigo"}}, {"user": {"id": 2, "name": "ezel"}}]' \
| jq '.[0] | keys' -c # ["user"]
$ echo '[{"user": {"id": 1, "name": "vigo"}}, {"user": {"id": 2, "name": "ezel"}}]' \
| jq '.[0].user | keys' -c # ["id","name"]
$ echo '{"id": 1, "name": "vigo"}' | jq 'has("id")' # true
$ echo '{"id": 1, "name": "vigo"}' | jq 'flatten' -c # [1,"vigo"]
$ echo '[{"user": {"id": 1, "name": "vigo"}}, {"user": {"id": 2, "name": "ezel"}}]' \
| jq '.[0].user | flatten' -c # [1,"vigo"]
to_entries
ile;
$ echo '{"id": 1, "name": "vigo"}' | jq 'to_entries'
[
{
"key": "id",
"value": 1
},
{
"key": "name",
"value": "vigo"
}
]
Şeklinde çıktı alırız. Diğer sevdiğim bir fonksiyon da select
:
# id’si 1 olanı alalım, select(.id == 1)
$ echo '[{"id": 1, "name": "vigo"}, {"id": 2, "name": "ezel"}]' \
| jq '.[] | select(.id == 1)'
{
"id": 1,
"name": "vigo"
}
Yaşı 10
’dan küçükleri filtreleyelim:
[
{
"id": 1,
"name": "vigo",
"age": 46
},
{
"id": 2,
"name": "ezel",
"age": 7
}
]
ve;
echo '[{"id": 1, "name": "vigo", "age": 46}, {"id": 2, "name": "ezel", "age": 7}]' \
| jq '.[] | select(.age < 10) | .name' # "ezel"
İşin püf noktası '.[] | select(.age < 10) | .name'
kısmında. .[]
ile liste
işlemi yapacağımızı, tek tek elemanı alacağımızı belirtip elemanı |
ile
select
fonksiyonuna pipe
ediyoruz. Fonksiyondan dönenin sadece .name
’ini
alıyoruz.
jq
içinde bir kısım fonksiyonlarla geliyor. Detaylara
buradan bakabilirsiniz.
Parantez, Array ve Obje Üreticileri
Parantez ile aynı matematik işlemlerindeki gibi öncelik ve gruplama işlerini yapabiliriz:
$ echo '1' | jq '(. + 2) * 5'
15
.
identity idi, yani 1
’in kendisi. (1 + 2) * 5 -> 3 * 5 -> 15
.
[]
ile Array Construction yani liste üretebiliriz:
{
"user": "vigo",
"badges": [
"python",
"ruby",
"golang"
]
}
Şimdi bir çıktı üretelim ve tipi liste olsun:
$ echo '{"user": "vigo", "badges": ["python", "ruby", "golang"]}' | jq '[.user, .badges]'
[
"vigo",
[
"python",
"ruby",
"golang"
]
]
Şimdi {}
ile Object Construction yapalım:
$ echo '{"user": "vigo", "badges": ["python", "ruby", "golang"]}' | jq '{user, stack: .badges}'
{
"user": "vigo",
"stack": [
"python",
"ruby",
"golang"
]
}
Aynı veriden multiple-dict üretelim:
$ echo '{"user": "vigo", "badges": ["python", "ruby", "golang"]}' | jq '{user, stack: .badges[]}'
{
"user": "vigo",
"stack": "python"
}
{
"user": "vigo",
"stack": "ruby"
}
{
"user": "vigo",
"stack": "golang"
}
Durum Kontrolleri
Elimizde şöyle bir veri var:
[
{
"name": "vigo",
"is_admin": true
},
{
"name": "lego",
"is_admin": false
}
]
Admin ve standart kullanıcıları görelim:
$ echo '[{"name": "vigo", "is_admin": true}, {"name": "lego", "is_admin": false}]' | jq '.[] | if .is_admin == true then (.name + " is admin") else (.name + " is standard user") end'
"vigo is admin"
"lego is standard user"
Eldeki veriyi csv
formatına çevirebiliriz:
$ echo '[{"id": 1, "name": "vigo", "age": 46}, {"id": 2, "name": "ezel", "age": 7}]' \
| jq -r '(map(keys) | add | unique) as $cols | map(. as $row | $cols | map($row[.])) as $rows | $cols, $rows[] | @csv'
"age","id","name"
46,1,"vigo"
7,2,"ezel"
Keza veriyi xpath
gibi görüntüleyebiliriz:
$ echo '[{"id": 1, "name": "vigo", "age": 46}, {"id": 2, "name": "ezel", "age": 7}]' | jq -r 'path(..) | map(tostring) | join("/")'
0
0/id
0/name
0/age
1
1/id
1/name
1/age
Konfigürasyon Dosyası
Eğer isterseniz kendiniz özelleştirilmiş fonksiyonlar yapıp kullanabilirsiniz.
Bunun için ~/.jq
dosyası oluşturmanız yeterli. Şimdi schema
diye
bir fonksiyon oluşturup ~/.jq
dosyasına ekleyelim:
$ echo 'def schema: path(..) | map(tostring) | join("/");' >> ~/.jq
şimdi kullanalım:
$ echo '{"data": {"users": [{"id": 1, "name": "vigo"},{"id": 2, "name": "ezel"}]}}' \
| jq 'schema'
""
"data"
"data/users"
"data/users/0"
"data/users/0/id"
"data/users/0/name"
"data/users/1"
"data/users/1/id"
"data/users/1/name"
jq
bir komut satırı araca olduğu için tüm dosya pipe
işlemleri de geçerli oluyor.
Kimi zaman pretty-print
yani çıktının güzel ve renkli görünmesi için, ya da dosyadan
okuyup başka bir yere paslamak için de kullanılır.
$ cat /path/to/file.json | jq
$ cat /path/to/file.json | jq > /path/to/output.json
$ curl -sL 'https://api.github.com/repos/vigo/statoo/commits?per_page=1' | jq
ya da;
$ curl -sL 'https://api.github.com/repos/vigo/statoo/commits?per_page=1' | jq '.[] | .commit.author'
{
"name": "Uğur Özyılmazel",
"email": "ugurozyilmazel@gmail.com",
"date": "2021-08-27T10:14:12Z"
}
Dediğim gibi, jq
benim gündelik hayatımda çok sık kullandığım bir araç. Ben
sadece bildiğim ve sık kullandığım konuları yazdım. Burada yazdıklarımdan çok
daha fazlası var. Umarım sizin de işinize yarar.