Redis'te JSON belgesi saklamak için JSON.SET anahtar $ '{...}', okumak için JSON.GET anahtar yol kullanırsınız. Yol, $ ile başlayan bir JSONPath ifadesidir; belgenin tamamı yerine tek bir alanı okuyabilir, JSON.NUMINCRBY ile bir sayıyı, JSON.ARRAPPEND ile bir diziyi atomik olarak güncelleyebilirsiniz. Birden çok belgeden aynı yolu JSON.MGET, alan silmeyi JSON.DEL, bir nesnenin anahtarlarını JSON.OBJKEYS verir.
Redis 8.0 sürüm notlarına göre Redis Community Edition'ın adı Redis Open Source oldu ve daha önce modül olarak kurulan JSON, Search, Time Series ve olasılıksal veri yapıları artık doğrudan pakete dahil. Yani güncel bir Redis Open Source sunucusunda ayrı bir modül yüklemeden JSON.* komutlarını kullanabilirsiniz. Daha eski sürümlerde bu komutlar ancak modül yüklüyse vardır. Serinin tamamı için Redis öğrenme rehberine bakabilirsiniz.
Neden string yerine JSON tipi?
JSON metnini serileştirip bir string anahtarına yazmak da yaygın bir yöntemdir; bunun için string komutları yazısına bakabilirsiniz. Belgenin kullanım senaryoları sayfası JSON tipinin üç avantajını sıralar. Birincisi, iç içe bir değeri tüm nesneyi ağ üzerinden taşımadan okuyabilirsiniz. İkincisi, bir sayıyı artırmak, diziye eleman eklemek ya da metne ek yapmak gibi kısmi güncellemeler atomiktir; string ile aynı işi yapmak için nesneyi okuyup yeniden serileştirmeniz gerekir ve bu hem pahalıdır hem atomik değildir. Üçüncüsü, JSON belgeleri Redis Search ile indekslenip sorgulanabilir; string içindeki JSON için bunun iyi bir yolu yoktur.
Hash ile karşılaştırınca fark iç içe yapıdadır. Hash düz bir alan-değer haritasıdır, alanların değerleri metindir; JSON ise iç içe nesne ve dizileri, sayı, boolean ve null tiplerini korur. Belge iç içe değilse ve alanlar düz metinse hash yeterli ve daha yalındır. Bu karar için hash yazısı ve veri tipi seçimi yazısı yardımcı olur.
İlk adımlar: JSON.SET ve JSON.GET

Veri tipi rehberinin ilk örneğinde kök yola bir JSON metni yazılır. Komutlardaki $, belgedeki değerin yoludur ve burada kök anlamına gelir:
> JSON.SET bike $ '"Hyperion"'
OK
> JSON.GET bike $
"[\"Hyperion\"]"
> JSON.TYPE bike $
1) "string"Yanıtın köşeli parantez içinde gelmesi bir hata değildir. JSONPath bir belgede birden fazla konuma çözülebildiği için $ ile başlayan bir yolla okuma yaptığınızda yanıt, eşleşen değerlerin üst düzey bir dizisi olarak döner. Değeri tek başına almak istiyorsanız istemci kodunda bu dizinin ilk elemanını alırsınız.
Metin değerinin kendisinin de geçerli bir JSON olması gerektiğine dikkat edin: "Hyperion" çift tırnaklı bir JSON metnidir ve redis-cli içinde bunu tek tırnakla sarmalarsınız. Sayılar, true, false ve null tırnaksız yazılır. Metinler için JSON.STRLEN uzunluğu verir, JSON.STRAPPEND ek yapar:
> JSON.STRLEN bike $
1) (integer) 8
> JSON.STRAPPEND bike $ '" (Enduro bikes)"'
1) (integer) 23
> JSON.GET bike $
"[\"Hyperion (Enduro bikes)\"]"JSON.SET kuralları
JSON.SET key path value [NX | XX] yolun çözüldüğü her konumdaki değeri değiştirir. Komut sayfası davranışı şöyle tanımlar:
- Anahtar yoksa yeni belge yalnızca kök yola (
$ya da.) yazılarak oluşturulabilir. Kök dışı bir yolla denersenizERR new objects must be created at the roothatası döner. - Yolun son parçası henüz olmayan bir nesne üyesiyse ve üst konum bir nesneyse, üye oluşturulur.
- Aradaki bir yol parçası yoksa yol oluşturulamaz ve komut boş yanıt döndürür.
NXyalnızca yolun hiç eşleşmesi yoksa,XXyalnızca en az bir eşleşme varsa yazar; koşul sağlanmazsa yanıt boştur.
> JSON.SET doc $ '{"a":2}'
OK
> JSON.SET doc $.b '8'
OK
> JSON.GET doc $
"[{\"a\":2,\"b\":8}]"
> JSON.SET doc $ 1
OK
> JSON.SET doc $.x.y 2
(nil)
> JSON.SET nonexistentkey $.x 5
(error) ERR new objects must be created at the rootİkinci gruptaki (nil) yanıtı genellikle gözden kaçar: komut hata vermez, sadece bir şey yazmaz. Uygulama kodunda JSON.SET yanıtını kontrol etmezseniz, aradaki bir nesne eksik olduğu için yazılmayan bir alanı fark etmeyebilirsiniz.
Komut sayfasında bir sınır daha belirtilir: komutlara gönderilen bir JSON değerinin iç içe derinliği en fazla 128 olabilir; daha derin bir nesne ya da dizi hata ile reddedilir.
JSONPath ile belgenin içine bakmak
Redis JSON iki yol sözdizimini destekler. Yol $ ile başlıyorsa JSONPath, başka bir karakterle başlıyorsa eski (legacy) sözdizimi kullanılır. JSONPath, Goessner'in tarif ettiği sözdizimini izler ve bir sorgu birden fazla konuma çözülebilir; komut bu durumda işlemi her konuma uygular. Eski sözdizimi ise yalnızca ilk konumda çalışır; belge bunu JSONPath desteğini eski yola göre büyük bir iyileştirme olarak sunar.
| Öğe | Anlamı |
|---|---|
| $ | Kök, yolu başlatır |
| . ya da [] | Alt öğeyi seçer |
| .. | Belgede özyinelemeli iner |
| * | Joker, tüm öğeler |
| [n] ve [-1] | Dizi elemanı, negatif sondan sayar |
| [start:end:step] | Dizi dilimi |
| ?() | Filtre ifadesi |
| @ | Filtre içinde geçerli öğe |
Belgedeki bisiklet envanteri örneğinde, aynı sonuca farklı yollarla ulaşılabildiği görülür. Bracket notasyonu, anahtar adında iki nokta üst üste ya da boşluk gibi özel karakterler olduğunda gerekir; redis-cli içinde çift tırnak kullanacaksanız yolu tek tırnakla sarmalayın:
> JSON.GET bikes:inventory $.inventory.mountain_bikes[*].model
"[\"Phoebe\",\"Quaoar\",\"Weywot\"]"
> JSON.GET bikes:inventory '$.inventory["mountain_bikes"][*].model'
"[\"Phoebe\",\"Quaoar\",\"Weywot\"]"
> JSON.GET bikes:inventory $..model
"[\"Phoebe\",\"Quaoar\",\"Weywot\",\"Salacia\",\"Mimas\"]"
> JSON.GET bikes:inventory $..mountain_bikes[0:2].model
"[\"Phoebe\",\"Quaoar\"]"Filtreler
?() filtreleri bir dizinin elemanlarını ya da bir nesnenin değerlerini tek tek gezer ve koşulu sağlayanları döndürür. Karşılaştırma operatörleri, && ve || mantıksal operatörleri ve parantezler kullanılabilir. Filtre içinde @ geçerli öğeyi, $ belgenin kökünü gösterir:
> JSON.GET bikes:inventory '$..[?(@.specs.material == "alloy")].model'
"[\"Weywot\",\"Mimas\"]"
> JSON.GET bikes:inventory '$..[?(@.specs.material =~ "(?i)al")].model'
"[\"Quaoar\",\"Weywot\",\"Salacia\",\"Mimas\"]"=~ operatörü düzenli ifade eşleşmesi yapar ve belgeye göre eşleşme kısmidir: "foo" deseni "barefoots" metniyle eşleşir. Tam eşleşme için "^foo$" yazmalısınız. Başka JSONPath motorlarında eğik çizgi arasındaki desenler tam eşleşme yaptığı için bu fark, başka bir sistemden taşınan sorgularda sürpriz yaratabilir. Metin olmayan değerler bu operatörle hiçbir zaman eşleşmez.
Kısmi ve atomik güncellemeler

JSON tipinin asıl gücü, belgeyi istemciye çekmeden bir parçasını değiştirebilmenizdir. Sayılar için JSON.NUMINCRBY ve JSON.NUMMULTBY vardır; ikisi de yeni değeri, eşleşen konumların dizisi olarak döndürür:
> JSON.SET crashes $ 0
OK
> JSON.NUMINCRBY crashes $ 1
"[1]"
> JSON.NUMINCRBY crashes $ 1.5
"[2.5]"
> JSON.NUMINCRBY crashes $ -0.75
"[1.75]"
> JSON.NUMMULTBY crashes $ 24
"[42.0]"Yol birden çok konuma çözülüyorsa işlem hepsine uygulanır. Belgedeki örnekte envanterdeki tüm fiyatlar tek komutla 100 düşürülüp geri artırılır; ardından bir filtreyle yalnızca 2000'den ucuz bisikletlerin fiyatı 1500 yapılır:
> JSON.GET bikes:inventory $..price
"[1920,2072,3264,1475,3941]"
> JSON.NUMINCRBY bikes:inventory $..price -100
"[1820,1972,3164,1375,3841]"
> JSON.NUMINCRBY bikes:inventory $..price 100
"[1920,2072,3264,1475,3941]"
> JSON.SET bikes:inventory '$.inventory.*[?(@.price<2000)].price' 1500
OK
> JSON.GET bikes:inventory $..price
"[1500,2072,3264,1500,3941]"Diziler için ayrı bir komut ailesi vardır: JSON.ARRAPPEND sona ekler, JSON.ARRINSERT belirli bir indekse ekler, JSON.ARRTRIM verilen aralığı bırakıp gerisini atar, JSON.ARRPOP son elemanı çıkarıp döndürür. Her biri yeni dizi uzunluğunu ya da çıkarılan değeri, eşleşen her konum için ayrı bir eleman olarak verir:
> JSON.SET riders $ []
OK
> JSON.ARRAPPEND riders $ '"Norem"'
1) (integer) 1
> JSON.ARRINSERT riders $ 1 '"Prickett"' '"Royce"' '"Castilla"'
1) (integer) 4
> JSON.ARRTRIM riders $ 1 1
1) (integer) 1
> JSON.GET riders $
"[[\"Prickett\"]]"
> JSON.ARRPOP riders $
1) "\"Prickett\""
> JSON.ARRPOP riders $
1) (nil)Nesneler için JSON.OBJLEN alan sayısını, JSON.OBJKEYS alan adlarını döndürür:
> JSON.SET bike:1 $ '{"model": "Deimos", "brand": "Ergonom", "price": 4972}'
OK
> JSON.OBJLEN bike:1 $
1) (integer) 3
> JSON.OBJKEYS bike:1 $
1) 1) "model"
2) "brand"
3) "price"JSON.MGET ve JSON.DEL
JSON.MGET key [key ...] path aynı yolu birden çok anahtardan okur. Var olmayan bir anahtar ya da yol için o konumda boş değer döner; istek bütünüyle başarısız olmaz:
> JSON.SET doc1 $ '{"a":1, "b": 2, "nested": {"a": 3}, "c": null}'
OK
> JSON.SET doc2 $ '{"a":4, "b": 5, "nested": {"a": 6}, "c": null}'
OK
> JSON.MGET doc1 doc2 $..a
1) "[1,3]"
2) "[4,6]"JSON.DEL key [path] yolun çözüldüğü değerleri siler ve silinen yol sayısını döndürür; olmayan yollar yok sayılır. Kökü silmek anahtarı silmekle aynıdır. Daha az bilinen bir kural da şudur: bir silme işlemi kök nesneyi ya da diziyi boş bırakırsa anahtarın kendisi de silinir:
> JSON.SET doc $ '{"a": 1, "nested": {"a": 2, "b": 3}}'
OK
> JSON.DEL doc $..a
(integer) 2
> JSON.GET doc $
"[{\"nested\":{\"b\":3}}]"
> JSON.SET doc $ '{"a": 1}'
OK
> JSON.DEL doc $.a
(integer) 1
> EXISTS doc
(integer) 0Yanıt biçimi: neden her şey dizi içinde?
JSON.GET sayfası yanıtın biçimini yola göre açıklar. Tek bir JSONPath ile yanıt, eşleşen değerlerin üst düzey bir dizisidir. Birden fazla yol verirseniz yanıt, her yolun anahtar olduğu ve değerinin bir dizi olduğu bir nesnedir; nesnedeki anahtarların sırası garanti edilmez. Eski sözdizimiyle tek değer döner. JSONPath ve eski yollar karışırsa yanıt JSONPath biçimine uyar:
> JSON.SET doc $ '{"a":2, "b": 3, "nested": {"a": 4, "b": null}}'
OK
> JSON.GET doc $..b
"[3,null]"
> JSON.GET doc ..a $..b
"{\"$..b\":[3,null],\"..a\":[2,4]}"redis-cli içinde okunabilir çıktı için redis-cli --raw ile bağlanıp JSON.GET komutuna INDENT, NEWLINE ve SPACE verebilirsiniz. redis-cli seçenekleri için redis-cli yazısına bakın.
Bellek kullanımı
Redis JSON, belgeleri ayrıştırdıktan sonra ikili bir ağaç yapısında saklar; bu, alt öğelere hızlı erişim sağlar ama çoğu zaman serileştirilmiş metinden daha fazla yer kaplar. RAM sayfasındaki değerler Redis 8 ile ölçülmüştür ve sürüme göre değişebilir. 64 bit sistemlerde her JSON değeri en az 8 bayt tutar. Null, boolean, küçük tamsayılar ve boş metin, dizi ya da nesne bu 8 baytla yetinir. Metinler uzunlukları kadar ek yer ister; örneğin 3 karakterlik bir metin 11 bayt eder.
Diziler kapasiteyle büyür: 1 ile 4 eleman arasındaki bir dizi 56 bayt tutar (8 bayt işaretçi, 16 bayt meta veri, başlangıç kapasitesi olan 4 eleman için 32 bayt). Beşinci eleman eklendiğinde kapasite ikiye katlanır ve dizi 88 bayta çıkar. Belgenin önemli bir uyarısı vardır: kaplardan değer silmek, kabın ayrılmış belleğini serbest bırakmaz. Ayrıca Redis aynı metni hem aynı belgede hem aynı düğümdeki farklı belgelerde tek kopya olarak saklar; JSON.DEBUG MEMORY ise her örneği ayrı sayarak raporlar, yani gerçek kullanımdan yüksek bir değer gösterebilir. Genel bellek ölçümü için MEMORY USAGE yazısına bakabilirsiniz.
Uygulamalı mini örnek: kullanıcı profili
Bir kullanıcı profilini JSON olarak tuttuğunuzu düşünün. Aşağıdaki komutlar belgelerdeki sözdizimine göre yazıldı; yanıtları göstermiyoruz, her birinin ne döndürdüğünü komut sayfalarına göre açıklıyoruz.
> JSON.SET kullanici:42 $ '{"ad": "Ayşe", "puan": 0, "etiketler": [], "adres": {"il": "İzmir"}}'
> JSON.NUMINCRBY kullanici:42 $.puan 10
> JSON.ARRAPPEND kullanici:42 $.etiketler '"premium"'
> JSON.SET kullanici:42 $.adres.ilce '"Bornova"'
> JSON.SET kullanici:42 $.fatura.vergiNo '"123"'
> JSON.GET kullanici:42 $.ad $.puanİlk komut kök yola yazdığı için yeni belgeyi oluşturur ve OK döner. İkinci komut puanı 10 artırır ve yeni değeri dizi içinde döndürür. Üçüncü komut etiket dizisine eleman ekler ve yeni uzunluğu verir. Dördüncü komut, üst nesne adres var olduğu için yeni ilce üyesini oluşturur. Beşinci komut ise boş yanıt döner: fatura nesnesi olmadığı için aradaki yol oluşturulamaz. Önce $.fatura yoluna boş bir nesne yazmanız gerekir. Son komut iki yol istediği için yanıt, anahtarları yollar olan bir JSON nesnesidir. Bu belgeleri e-posta ya da il alanına göre aramak isterseniz bir Search indeksi tanımlamanız gerekir; bu konu FT.CREATE ve FT.SEARCH yazısında işleniyor.
Sık yapılan hatalar

- Yeni anahtara kök dışı yolla yazmak.
ERR new objects must be created at the roothatası alırsınız. (nil)yanıtını görmezden gelmek. Aradaki nesne eksikse ya daNX/XXkoşulu sağlanmazsa hiçbir şey yazılmaz.- Değeri JSON olarak tırnaklamamak. Metin değeri
'"Bornova"'şeklinde, iç çift tırnaklarla gönderilmelidir. - Yanıtın dizi olduğunu unutmak.
$yollarında tek değer bile dizi içinde döner. =~ile tam eşleşme beklemek. Eşleşme kısmidir;^ve$ile sabitleyin.- Son alanı silince anahtarın kalacağını sanmak. Kök boş kalırsa anahtar silinir.
Sık Sorulan Sorular
Redis JSON için modül kurmam gerekir mi?
Redis 8 ve sonrasında Redis Open Source paketinde JSON doğrudan bulunur. Daha eski sürümlerde ayrı modül gerekir.
JSONPath ile eski yol sözdizimi arasındaki fark nedir?
$ ile başlayan yollar JSONPath'tir ve tüm eşleşmelere uygulanır, yanıtı dizi olarak verir. Diğer yollar eski sözdizimidir, yalnızca ilk eşleşmede çalışır ve tek değer döndürür.
Belgedeki bir alanı sorgulayarak belge bulabilir miyim?
JSON.GET yalnızca bildiğiniz anahtarı okur. Alan değerine göre belge bulmak için Redis Search ile JSON üzerine indeks tanımlarsınız.
Belgenin iç içe derinliği sınırlı mı?
Evet. Komutlara gönderilen bir JSON değeri en fazla 128 düzey derinliğinde olabilir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Redis öğrenme rehberi · Redis Search · Hash komutları · String komutları · Veri tipi seçimi