Dejme tomu, že máme složitý objekt a rádi bychom jej konvertovali na ÅetÄzec, abychom jej poslali po sÃti nebo ho jen vypsali pro úÄely logovánÃ.
SamozÅejmÄ by takový ÅetÄzec mÄl obsahovat vÅ¡echny důležité vlastnosti.
Můžeme implementovat konverzi napÅÃklad takto:
let uživatel = {
jméno: "Jan",
vÄk: 30,
toString() {
return `{jméno: "${this.jméno}", vÄk: ${this.vÄk}}`;
}
};
alert(uživatel); // {jméno: "Jan", vÄk: 30}
â¦BÄhem procesu vývoje se vÅ¡ak pÅidávajà nové vlastnosti, staré se pÅejmenovávajà a odstraÅujÃ. Pokaždé mÄnit takový toString by mohlo být nepÅÃjemné. Mohli bychom se pokusit vytvoÅit cyklus nad vlastnostmi, ale co když je objekt složitý a obsahuje ve vlastnostech vnoÅené objekty? Museli bychom implementovat i jejich konverzi.
NaÅ¡tÄstà kód, který by tohle vÅ¡echno zvládl, nenà tÅeba psát. Tento úkol byl již vyÅeÅ¡en.
JSON.stringify
JSON (JavaScript Object Notation â JavaScriptová objektová notace) je obecný formát sloužÃcà k reprezentaci hodnot a objektů. Je popsán ve standardu RFC 4627. PůvodnÄ byl vytvoÅen pro JavaScript, ale knihovny pro práci s nÃm poskytuje i mnoho jiných jazyků. Je tedy snadné použÃvat JSON pro výmÄnu dat, když klient použÃvá JavaScript a server je napsán v Ruby, PHP, JavÄ nebo Äemkoli jiném.
JavaScript poskytuje metody:
JSON.stringifypro konverzi objektů na JSON.JSON.parsepro konverzi JSONu zpÄt na objekty.
NapÅÃklad zde zavoláme JSON.stringify na studenta:
let student = {
jméno: 'Jan',
vÄk: 30,
jeSprávce: false,
kurzy: ['html', 'css', 'js'],
choť: null
};
let json = JSON.stringify(student);
alert(typeof json); // zÃskali jsme ÅetÄzec!
alert(json);
/* objekt zakódovaný do JSON:
{
"jméno": "Jan",
"vÄk": 30,
"jeSprávce": false,
"kurzy": ["html", "css", "js"],
"choť": null
}
*/
Metoda JSON.stringify(student) vezme objekt a konvertuje ho na ÅetÄzec.
Výsledný ÅetÄzec json se nazývá JSONem zakódovaný, serializovaný, zÅetÄzený nebo marshallovaný objekt. Jsme pÅipraveni poslat ho po drátÄ nebo umÃstit ho do úložiÅ¡tÄ planých dat.
ProsÃme vÅ¡imnÄte si, že objekt zakódovaný JSONem má nÄkolik důležitých rozdÃlů oproti objektovému literálu:
- ÅetÄzce jsou uzavÅeny v dvojitých uvozovkách. JSON nezná jednoduché ani zpÄtné uvozovky. Z
'Jan'se tedy stane"Jan". - Názvy vlastnostà objektů jsou rovnÄž v dvojitých uvozovkách. To je povinné. Z
vÄk:30se tedy stane"vÄk":30.
JSON.stringify lze aplikovat i na primitivy.
JSON podporuje následujÃcà datové typy:
- Objekty
{ ... } - Pole
[ ... ] - Primitivy:
- ÅetÄzce,
- ÄÃsla,
- booleovské hodnoty
true/false, null.
PÅÃklad:
// ÄÃslo v JSON je prostÄ ÄÃslo
alert( JSON.stringify(1) ) // 1
// ÅetÄzec v JSON je stále ÅetÄzec, ale v uvozovkách
alert( JSON.stringify('test') ) // "test"
alert( JSON.stringify(true) ); // true
alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON je jazykovÄ nezávislá specifikace urÄená jen pro data, takže nÄkteré objektové vlastnosti specifické pro JavaScript JSON.stringify vynechává.
KonkrétnÄ:
- FunkÄnà vlastnosti (metody).
- Symbolick̩ kl̀e a hodnoty.
- Vlastnosti, v nichž je uloženo
undefined.
let uživatel = {
ÅekniAhoj() { // ignoruje se
alert("Ahoj");
},
[Symbol("id")]: 123, // ignoruje se
nÄco: undefined // ignoruje se
};
alert( JSON.stringify(uživatel) ); // {} (prázdný objekt)
Obvykle je to dobÅe. Pokud to chceme nÄjak jinak, brzy uvidÃme, jak si tento proces můžeme pÅizpůsobit.
SkvÄlé je, že vnoÅené objekty jsou podporovány a automaticky se konvertujÃ.
PÅÃklad:
let mÃtink = {
titul: "Konference",
mÃstnost: {
ÄÃslo: 23,
úÄastnÃci: ["jan", "anna"]
}
};
alert( JSON.stringify(mÃtink) );
/* Celá struktura je zÅetÄzena:
{
"titul":"Konference",
"mÃstnost":{"ÄÃslo":23,"úÄastnÃci":["jan","anna"]},
}
*/
Důležité omezenÃ: v objektu nesmÄjà být kruhové odkazy.
PÅÃklad:
let mÃstnost = {
ÄÃslo: 23
};
let mÃtink = {
titul: "Konference",
úÄastnÃci: ["jan", "anna"]
};
mÃtink.mÃsto = mÃstnost; // mÃtink se odkazuje na mÃstnost
mÃstnost.obsazenoÄÃm = mÃtink; // mÃstnost se odkazuje na mÃtink
JSON.stringify(mÃtink); // Error: Converting circular structure to JSON
Zde konverze selže kvůli kruhovému odkazu: mÃstnost.obsazenoÄÃm se odkazuje na mÃtink a mÃtink.mÃsto se odkazuje na mÃstnost:
VylouÄenà a transformace: funkce nahrazenÃ
Ãplná syntaxe JSON.stringify je:
let json = JSON.stringify(hodnota[, nahrazenÃ, mezery])
- hodnota
- Hodnota k zakódovánÃ.
- nahrazenÃ
- Pole vlastnostÃ, které se majà zakódovat, nebo mapovacà funkce
function(klÃÄ, hodnota). - mezery
- PoÄet mezer, které se použijà pÅi formátovánÃ.
VÄtÅ¡inou se JSON.stringify použÃvá jen s prvnÃm argumentem. Jestliže vÅ¡ak potÅebujeme proces nahrazenà vyladit, napÅÃklad odfiltrovat kruhové odkazy, můžeme použÃt druhý argument funkce JSON.stringify.
PÅedáme-li do nÄj pole vlastnostÃ, budou zakódovány pouze vlastnosti uvedené v tomto poli.
PÅÃklad:
let mÃstnost = {
ÄÃslo: 23
};
let mÃtink = {
titul: "Konference",
úÄastnÃci: [{jméno: "Jan"}, {jméno: "Alice"}],
mÃsto: mÃstnost // mÃtink se odkazuje na mÃstnost
};
mÃstnost.obsazenoÄÃm = mÃtink; // mÃstnost se odkazuje na mÃtink
alert( JSON.stringify(mÃtink, ['titul', 'úÄastnÃci']) );
// {"titul":"Konference","úÄastnÃci":[{},{}]}
Tady jsme asi pÅÃliÅ¡ pÅÃsnÃ. Seznam vlastnostà se použije na celou strukturu. Objekty v poli úÄastnÃci jsou tedy prázdné, protože jméno nenà na seznamu.
ZahrÅme na seznam vÅ¡echny vlastnosti kromÄ mÃstnost.obsazenoÄÃm, která by způsobila kruhový odkaz:
let mÃstnost = {
ÄÃslo: 23
};
let mÃtink = {
titul: "Konference",
úÄastnÃci: [{jméno: "Jan"}, {jméno: "Alice"}],
mÃsto: mÃstnost // mÃtink se odkazuje na mÃstnost
};
mÃstnost.obsazenoÄÃm = mÃtink; // mÃstnost se odkazuje na mÃtink
alert( JSON.stringify(mÃtink, ['titul', 'úÄastnÃci', 'mÃsto', 'jméno', 'ÄÃslo']) );
/*
{
"titul":"Konference",
"úÄastnÃci":[{"jméno":"Jan"},{"jméno":"Alice"}],
"mÃsto":{"ÄÃslo":23}
}
*/
Nynà je serializováno vÅ¡echno kromÄ obsazenoÄÃm. Seznam vlastnostà je vÅ¡ak pomÄrnÄ dlouhý.
NaÅ¡tÄstà můžeme jako nahrazenà mÃsto pole použÃt funkci.
Tato funkce bude volána pro každou dvojici (klÃÄ, hodnota) a mÄla by vracet ânahrazenouâ hodnotu, která bude použita mÃsto původnÃ, nebo undefined, jestliže hodnota má být pÅeskoÄena.
V naÅ¡em pÅÃpadÄ můžeme vracet hodnotu hodnota tak, âjak jeâ, pro vÅ¡echno kromÄ obsazenoÄÃm. Abychom obsazenoÄÃm ignorovali, následujÃcà kód vrátà undefined:
let mÃstnost = {
ÄÃslo: 23
};
let mÃtink = {
titul: "Konference",
úÄastnÃci: [{jméno: "Jan"}, {jméno: "Alice"}],
mÃsto: mÃstnost // mÃtink se odkazuje na mÃstnost
};
mÃstnost.obsazenoÄÃm = mÃtink; // mÃstnost se odkazuje na mÃtink
alert( JSON.stringify(mÃtink, function nahrazenÃ(klÃÄ, hodnota) {
alert(`${klÃÄ}: ${hodnota}`);
return (klÃÄ == 'obsazenoÄÃm') ? undefined : hodnota;
}));
/* dvojice klÃÄ:hodnota, které vstoupily do nahrazenÃ:
: [object Object]
titul: Konference
úÄastnÃci: [object Object],[object Object]
0: [object Object]
jméno: Jan
1: [object Object]
jméno: Alice
mÃsto: [object Object]
ÄÃslo: 23
obsazenoÄÃm: [object Object]
*/
ProsÃme vÅ¡imnÄte si, že funkce nahrazenà obdržà každou dvojici klÃÄ/hodnota vÄetnÄ vnoÅených objektů a prvků pole. Aplikuje se rekurzÃvnÄ. Hodnota this uvnitÅ funkce nahrazenà je objekt, který obsahuje aktuálnà vlastnost.
Prvnà volánà je speciálnÃ. UÄinà se pomocà speciálnÃho âobalového objektuâ: {"": mÃtink}. Jinými slovy, prvnà dvojice (klÃÄ, hodnota) má prázdný klÃÄ a hodnotou je celý cÃlový objekt. Z tohoto důvodu je prvnà Åádek ve výše uvedeném pÅÃkladu ":[object Object]".
Stojà za tÃm úmysl poskytnout funkci nahrazenà co nejvÄtšà moc: má možnost analyzovat a nahradit Äi pÅeskoÄit i celý objekt, je-li to nutné.
FormátovánÃ: mezery
TÅetà argument JSON.stringify(hodnota, nahrazenÃ, mezery) je poÄet mezer, které se použijà pro pÄkné formátovánÃ.
Žádné objekty pÅevedené na ÅetÄzce, které jsme doposud uvedli, nemÄly odsazenà a mezery navÃc. To je dobÅe, jestliže chceme poslat objekt po sÃti. Argument mezery se použÃvá výhradnÄ pro zkrášlenà výstupu.
Zde mezery = 2 ÅÃká JavaScriptu, aby zobrazil vnoÅené objekty na vÃce ÅádcÃch s odsazenÃm 2 mezery uvnitÅ objektu:
let uživatel = {
jméno: "Jan",
vÄk: 25,
role: {
jeSprávce: false,
jeEditor: true
}
};
alert(JSON.stringify(uživatel, null, 2));
/* dvoumezerová odsazenÃ:
{
"jméno": "Jan",
"vÄk": 25,
"role": {
"jeSprávce": false,
"jeEditor": true
}
}
*/
/* pro JSON.stringify(uživatel, null, 4) by výsledek byl vÃce odsazený:
{
"jméno": "Jan",
"vÄk": 25,
"role": {
"jeSprávce": false,
"jeEditor": true
}
}
*/
TÅetà argument může být i ÅetÄzec. V tom pÅÃpadÄ se pro odsazenà mÃsto stanoveného poÄtu mezer použije tento ÅetÄzec.
Parametr mezery se použÃvá výhradnÄ pro úÄely logovánà a pÄkného výstupu.
Vlastnà âtoJSONâ
StejnÄ jako toString pro konverzi na ÅetÄzec může objekt poskytovat i metodu toJSON pro konverzi na JSON. Pokud je k dispozici, JSON.stringify ji automaticky zavolá.
PÅÃklad:
let mÃstnost = {
ÄÃslo: 23
};
let mÃtink = {
titul: "Konference",
datum: new Date(Date.UTC(2017, 0, 1)),
mÃstnost
};
alert( JSON.stringify(mÃtink) );
/*
{
"titul":"Konference",
"datum":"2017-01-01T00:00:00.000Z", // (1)
"mÃstnost": {"ÄÃslo":23} // (2)
}
*/
Zde vidÃme, že datum (1) se stalo ÅetÄzcem. Je to tÃm, že vÅ¡echna data majà vestavÄnou metodu toJSON, která vrátà ÅetÄzec tohoto druhu.
Nynà pÅidáme vlastnà metodu toJSON do naÅ¡eho objektu mÃstnost (2):
let mÃstnost = {
ÄÃslo: 23,
toJSON() {
return this.ÄÃslo;
}
};
let mÃtink = {
titul: "Konference",
mÃstnost
};
alert( JSON.stringify(mÃstnost) ); // 23
alert( JSON.stringify(mÃtink) );
/*
{
"titul":"Konference",
"mÃstnost": 23
}
*/
Jak vidÃme, toJSON se použÃvá jak pÅi pÅÃmém volánà JSON.stringify(mÃstnost), tak tehdy, když je mÃstnost vnoÅená v jiném kódovaném objektu.
JSON.parse
K dekódovánà ÅetÄzce v JSONu potÅebujeme dalšà metodu, která se nazývá JSON.parse.
Syntaxe:
let hodnota = JSON.parse(ÅetÄzec[, oživenÃ]);
- ÅetÄzec
- ÅetÄzec v JSONu, který se má dekódovat.
- oživenÃ
- Nepovinná funkce
function(klÃÄ, hodnota), která bude volána pro každou dvojici(klÃÄ, hodnota)a může hodnotu transformovat.
PÅÃklad:
// zÅetÄzené pole
let ÄÃsla = "[0, 1, 2, 3]";
ÄÃsla = JSON.parse(ÄÃsla);
alert( ÄÃsla[1] ); // 1
Nebo pro vnoÅené objekty:
let dataUživatele = '{ "jméno": "Jan", "vÄk": 35, "jeSprávce": false, "pÅátelé": [0,1,2,3] }';
let uživatel = JSON.parse(dataUživatele);
alert( uživatel.pÅátelé[1] ); // 1
JSON může být tak složitý, jak potÅebujeme, objekty a pole mohou obsahovat jiné objekty a pole. VÅ¡echny vÅ¡ak musejà dodržovat stejný formát JSON.
Toto jsou typické chyby v ruÄnÄ psaném JSONu (nÄkdy jej musÃme napsat ruÄnÄ pro úÄely ladÄnÃ):
let json = `{
jméno: "Jan", // chyba: název vlastnosti bez uvozovek
"pÅÃjmenÃ": 'Novák', // chyba: hodnota v jednoduchých uvozovkách (musà být ve dvojitých)
'jeSprávce': false // chyba: klÃÄ v jednoduchých uvozovkách (musà být ve dvojitých)
"datumNarozenÃ": new Date(2000, 2, 3), // chyba: nenà povoleno "new", jen Äisté hodnoty
"pÅátelé": [0,1,2,3] // zde je vÅ¡e v poÅádku
}`;
KromÄ toho JSON nepodporuje komentáÅe. PÅidánà komentáÅe do JSONu jej uÄinà neplatným.
Existuje i jiný formát jménem JSON5, který umožÅuje klÃÄe bez uvozovek, komentáÅe a podobnÄ. To je vÅ¡ak samostatná knihovna, která nenà obsažena ve specifikaci jazyka.
Standardnà JSON je tak pÅÃsný ne proto, že by jeho vývojáÅi byli lÃnÃ, ale proto, aby umožnil snadnou, spolehlivou a velmi rychlou implementaci naÄÃtacÃho algoritmu.
Použità oživenÃ
PÅedstavme si, že jsme ze serveru zÃskali zÅetÄzený objekt mÃtink.
Vypadá takto:
// titul: (titul mÃtinku), datum: (datum mÃtinku)
let ÅetÄzec = '{"titul":"Konference","datum":"2017-11-30T12:00:00.000Z"}';
â¦A nynà jej musÃme deserializovat, aby se z nÄj znovu stal JavaScriptový objekt.
UÄinÃme to volánÃm JSON.parse:
let ÅetÄzec = '{"titul":"Konference","datum":"2017-11-30T12:00:00.000Z"}';
let mÃtink = JSON.parse(ÅetÄzec);
alert( mÃtink.datum.getDate() ); // Chyba!
Ouha! Chyba!
Hodnota mÃtink.datum je ÅetÄzec, ne objekt Date. Jak mohl JSON.parse vÄdÄt, že má tento ÅetÄzec transformovat do objektu Date?
Jako druhý argument pÅedáme do JSON.parse funkci oživenÃ, která vrátà vÅ¡echny hodnoty tak, âjak jsouâ, ale datum zmÄnà na Date:
let ÅetÄzec = '{"titul":"Konference","datum":"2017-11-30T12:00:00.000Z"}';
let mÃtink = JSON.parse(ÅetÄzec, function(klÃÄ, hodnota) {
if (klÃÄ == 'datum') return new Date(hodnota);
return hodnota;
});
alert( mÃtink.datum.getDate() ); // teÄ to funguje!
Mimochodem, funguje to stejnÄ i pro vnoÅené objekty:
let rozvrh = `{
"seznamMÃtinků": [
{"titul":"Konference","datum":"2017-11-30T12:00:00.000Z"},
{"titul":"Narozeniny","datum":"2017-04-18T12:00:00.000Z"}
]
}`;
rozvrh = JSON.parse(rozvrh, function(klÃÄ, hodnota) {
if (klÃÄ == 'datum') return new Date(hodnota);
return hodnota;
});
alert( rozvrh.seznamMÃtinků[1].datum.getDate() ); // funguje to!
ShrnutÃ
- JSON je datový formát, který má svůj nezávislý standard a knihovny pro vÄtÅ¡inu programovacÃch jazyků.
- JSON podporuje plané objekty, pole, ÅetÄzce, ÄÃsla, booleany a
null. - JavaScript poskytuje metody JSON.stringify pro serializaci do JSONu a JSON.parse pro naÄÃtánà z JSONu.
- ObÄ metody podporujà transformaÄnà funkce pro vylepÅ¡ené naÄÃtánà a zápis.
- Má-li objekt metodu
toJSON, pak ji funkceJSON.stringifyzavolá.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)