Допустим, у нас есть сложный объект, и мы хотели бы преобразовать его в строку, отправить по сети или просто вывести для целей протоколирования.
Естественно, такая строка должна включать в себя все важные свойства.
Мы могли бы реализовать преобразование следующим образом:
пусть пользователь = { имя: «Джон», возраст: 30, toString() { return `{name: "${this.name}", age: ${this.age}}`; } }; оповещение (пользователь); // {имя: "Джон", возраст: 30}
…Но в процессе разработки добавляются новые свойства, переименовываются и удаляются старые свойства. Обновление такого toString
каждый раз может стать проблемой. Мы могли бы попытаться перебрать свойства в нем, но что, если объект сложный и имеет вложенные объекты в свойствах? Нам также нужно будет реализовать их преобразование.
К счастью, нет необходимости писать код, чтобы справиться со всем этим. Задача уже решена.
JSON (нотация объектов JavaScript) — это общий формат для представления значений и объектов. Он описан в стандарте RFC 4627. Изначально он был создан для JavaScript, но во многих других языках также есть библиотеки для его обработки. Поэтому легко использовать JSON для обмена данными, когда клиент использует JavaScript, а сервер написан на Ruby/PHP/Java/что угодно.
JavaScript предоставляет методы:
JSON.stringify
для преобразования объектов в JSON.
JSON.parse
для преобразования JSON обратно в объект.
Например, здесь мы JSON.stringify
студента:
пусть студент = { имя: 'Джон', возраст: 30, isAdmin: ложь, курсы: ['html', 'css', 'js'], супруг: ноль }; пусть json = JSON.stringify(студент); оповещение (тип JSON); // у нас есть строка! оповещение (JSON); /* Объект в формате JSON: { "имя": "Джон", «возраст»: 30, «isAdmin»: ложь, "курсы": ["html", "css", "js"], "супруга": ноль } */
Метод JSON.stringify(student)
принимает объект и преобразует его в строку.
Результирующая строка json
называется объектом в кодировке JSON , сериализованным , строковым или маршаллизованным объектом. Мы готовы отправить его по сети или поместить в обычное хранилище данных.
Обратите внимание, что объект в кодировке JSON имеет несколько важных отличий от литерала объекта:
В строках используются двойные кавычки. Никаких одинарных кавычек и обратных кавычек в JSON. Итак, 'John'
становится "John"
.
Имена свойств объектов также заключаются в двойные кавычки. Это обязательно. Таким образом, age:30
становится "age":30
.
JSON.stringify
также можно применять к примитивам.
JSON поддерживает следующие типы данных:
Объекты { ... }
Массивы [ ... ]
Примитивы:
струны,
цифры,
логические значения true/false
,
null
.
Например:
// число в JSON — это просто число предупреждение( JSON.stringify(1) ) // 1 // строка в JSON по-прежнему является строкой, но заключена в двойные кавычки alert( JSON.stringify('test')) // "тест" оповещение(JSON.stringify(истина)); // истинный alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON — это независимая от языка спецификация только для данных, поэтому некоторые свойства объекта, специфичные для JavaScript, пропускаются JSON.stringify
.
А именно:
Свойства функции (методы).
Символические ключи и значения.
Свойства, хранящие undefined
.
пусть пользователь = { SayHi() { // игнорируется Оповещение("Привет"); }, [Symbol("id")]: 123, // игнорируется что-то: неопределенное // игнорируется }; предупреждение(JSON.stringify(пользователь)); // {} (пустой объект)
Обычно это нормально. Если это не то, что нам нужно, то скоро мы увидим, как настроить этот процесс.
Самое замечательное то, что вложенные объекты поддерживаются и преобразуются автоматически.
Например:
пусть встреча = { название: «Конференция», комната: { номер: 23, участники: ["Джон", "Энн"] } }; оповещение(JSON.stringify(встреча)); /* Вся структура является строковой: { "title":"Конференция", "room":{"number":23,"participants":["john","ann"]}, } */
Важное ограничение: не должно быть циклических ссылок.
Например:
пусть комната = { номер: 23 }; пусть встреча = { название: «Конференция», участники: ["Джон", "Энн"] }; встреча.место = комната; // комната для справок о встречах room.occupiedBy = встреча; // встреча по ссылкам на комнаты JSON.stringify(встреча); // Ошибка: преобразование круговой структуры в JSON
Здесь преобразование завершается неудачей из-за циклической ссылки: room.occupiedBy
ссылается на meetup
и meetup.place
ссылается на room
:
Полный синтаксис JSON.stringify
:
let json = JSON.stringify(значение[, заменитель, пробел])
ценить
Значение для кодирования.
заменитель
Массив свойств для кодирования или функция сопоставления function(key, value)
.
космос
Объем пространства, используемого для форматирования
Большую часть времени JSON.stringify
используется только с первым аргументом. Но если нам нужно точно настроить процесс замены, например отфильтровать циклические ссылки, мы можем использовать второй аргумент JSON.stringify
.
Если мы передадим ему массив свойств, будут закодированы только эти свойства.
Например:
пусть комната = { номер: 23 }; пусть встреча = { название: «Конференция», участники: [{name: «Джон»}, {name: «Алиса»}], место: комната // комната для справок о встречах }; room.occupiedBy = встреча; // встреча по ссылкам на комнаты alert( JSON.stringify(meetup, ['title', 'участники']) ); // {"title":"Конференция","участники":[{},{}]}
Здесь мы, наверное, слишком строги. Список свойств применяется ко всей структуре объекта. Итак, объекты в participants
пусты, потому что name
нет в списке.
Давайте включим в список все свойства, кроме room.occupiedBy
, которые могут вызвать циклическую ссылку:
пусть комната = { номер: 23 }; пусть встреча = { название: «Конференция», участники: [{name: «Джон»}, {name: «Алиса»}], место: комната // комната для справок о встречах }; room.occupiedBy = встреча; // встреча по ссылкам на комнаты alert( JSON.stringify(meetup, ['название', 'участники', 'место', 'имя', 'номер']) ); /* { "title":"Конференция", "участники":[{"name":"Джон"},{"name":"Алиса"}], "место":{"номер":23} } */
Теперь все, кроме occupiedBy
сериализуется. Но список свойств довольно длинный.
К счастью, в качестве replacer
мы можем использовать функцию вместо массива.
Функция будет вызываться для каждой пары (key, value)
и должна возвращать «замещенное» значение, которое будет использоваться вместо исходного. Или undefined
, если значение нужно пропустить.
В нашем случае мы можем вернуть value
«как есть» для всего, кроме occupiedBy
. Чтобы игнорировать occupiedBy
, приведенный ниже код возвращает undefined
:
пусть комната = { номер: 23 }; пусть встреча = { название: «Конференция», участники: [{name: «Джон»}, {name: «Алиса»}], место: комната // комната для справок о встречах }; room.occupiedBy = встреча; // встреча по ссылкам на комнаты alert( JSON.stringify(meetup, function replacer(key, value) { alert(`${key}: ${value}`); return (ключ == 'occupiedBy')? неопределенное: значение; })); /* пары ключ:значение, которые приходят в replacer: : [объект Объект] название: Конференция участники: [Объект объекта],[Объект объекта] 0: [объект Объект] имя: Джон 1: [объект Объект] имя: Алиса место: [объект Объект] номер: 23 оккупировано: [объект Объект] */
Обратите внимание, что функция replacer
получает каждую пару ключ/значение, включая вложенные объекты и элементы массива. Он применяется рекурсивно. Значением this
внутреннего replacer
является объект, содержащий текущее свойство.
Первый звонок особенный. Это делается с помощью специального «объекта-обертки»: {"": meetup}
. Другими словами, первая пара (key, value)
имеет пустой ключ, а значением является целевой объект в целом. Вот почему в приведенном выше примере первая строка — ":[object Object]"
.
Идея состоит в том, чтобы предоставить replacer
как можно больше возможностей: у него есть возможность проанализировать и заменить/пропустить даже весь объект, если это необходимо.
Третий аргумент JSON.stringify(value, replacer, space)
— это количество пробелов, используемых для красивого форматирования.
Раньше все строковые объекты не имели отступов и дополнительных пробелов. Это нормально, если мы хотим отправить объект по сети. Аргумент space
используется исключительно для хорошего вывода.
Здесь space = 2
указывает JavaScript отображать вложенные объекты в нескольких строках с отступом в 2 пробела внутри объекта:
пусть пользователь = { имя: «Джон», возраст: 25, роли: { isAdmin: ложь, isEditor: правда } }; alert(JSON.stringify(user, null, 2)); /* отступы в два пробела: { "имя": "Джон", «возраст»: 25, "роли": { «isAdmin»: ложь, «isEditor»: правда } } */ /* для JSON.stringify(user, null, 4) результат будет с большим отступом: { "имя": "Джон", «возраст»: 25, "роли": { «isAdmin»: ложь, «isEditor»: правда } } */
Третий аргумент также может быть строкой. В этом случае для отступа используется строка, а не ряд пробелов.
Параметр space
используется исключительно для целей протоколирования и корректного вывода.
Как и toString
для преобразования строк, объект может предоставлять метод toJSON
для преобразования в JSON. JSON.stringify
автоматически вызывает его, если он доступен.
Например:
пусть комната = { номер: 23 }; пусть встреча = { название: «Конференция», дата: новая дата(Date.UTC(2017, 0, 1)), комната }; оповещение(JSON.stringify(встреча)); /* { "title":"Конференция", "date":"2017-01-01T00:00:00.000Z", // (1) "комната": {"номер":23} // (2) } */
Здесь мы видим, что date
(1)
стала строкой. Это потому, что все даты имеют встроенный метод toJSON
, который возвращает такую строку.
Теперь давайте добавим собственный toJSON
для нашей объектной room
(2)
:
пусть комната = { номер: 23, toJSON() { вернуть этот.номер; } }; пусть встреча = { название: «Конференция», комната }; предупреждение(JSON.stringify(комната)); // 23 оповещение(JSON.stringify(встреча)); /* { "title":"Конференция", "комната": 23 } */
Как мы видим, toJSON
используется как для прямого вызова JSON.stringify(room)
, так и когда room
вложена в другой закодированный объект.
Чтобы декодировать JSON-строку, нам нужен другой метод с именем JSON.parse.
Синтаксис:
let value = JSON.parse(str[, reviver]);
ул.
JSON-строка для анализа.
оживить
Необязательная функция (ключ, значение), которая будет вызываться для каждой пары (key, value)
и может преобразовывать значение.
Например:
// строковый массив пусть числа = "[0, 1, 2, 3]"; числа = JSON.parse(числа); предупреждение(числа[1]); // 1
Или для вложенных объектов:
let userData = '{ "name": "Джон", "возраст": 35, "isAdmin": false, "друзья": [0,1,2,3] }'; пусть пользователь = JSON.parse(userData); оповещение(пользователь.друзья[1]); // 1
JSON может быть настолько сложным, насколько это необходимо, объекты и массивы могут включать в себя другие объекты и массивы. Но они должны подчиняться одному и тому же формату JSON.
Вот типичные ошибки в рукописном JSON (иногда нам приходится писать его в целях отладки):
пусть json = `{ name: "John", // ошибка: имя свойства без кавычек "фамилия": 'Смит', // ошибка: значение в одинарных кавычках (должно быть двойных) 'isAdmin': false // ошибка: в ключе одинарные кавычки (должны быть двойные) "день рождения": новая дата(2000, 2, 3), // ошибка: "новое" не допускается, только голые значения "friends": [0,1,2,3] // здесь все в порядке }`;
Кроме того, JSON не поддерживает комментарии. Добавление комментария в JSON делает его недействительным.
Есть еще один формат, JSON5, который позволяет использовать ключи без кавычек, комментарии и т. д. Но это отдельная библиотека, а не в спецификации языка.
Обычный JSON настолько строг не потому, что его разработчики ленивы, а потому, что он позволяет легко, надежно и очень быстро реализовать алгоритм синтаксического анализа.
Представьте себе, что мы получили с сервера строковый объект meetup
.
Это выглядит так:
// title: (название встречи), date: (дата встречи) let str = '{"title":"Конференция","date":"2017-11-30T12:00:00.000Z"}';
…И теперь нам нужно его десериализовать , чтобы снова превратить в объект JavaScript.
Давайте сделаем это, вызвав JSON.parse
:
let str = '{"title":"Конференция","date":"2017-11-30T12:00:00.000Z"}'; пусть встреча = JSON.parse(str); оповещение(meetup.date.getDate()); // Ошибка!
Упс! Ошибка!
Значение meetup.date
— это строка, а не объект Date
. Откуда JSON.parse
мог знать, что он должен преобразовать эту строку в Date
?
Давайте передадим в JSON.parse
функцию возрождения в качестве второго аргумента, которая возвращает все значения «как есть», но date
станет Date
:
let str = '{"title":"Конференция","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str, function(key, value) { if (key == 'date') вернуть новую дату (значение); возвращаемое значение; }); оповещение(meetup.date.getDate()); // теперь работает!
Кстати, это работает и для вложенных объектов:
пусть расписание = `{ "встречи": [ {"title":"Конференция","date":"2017-11-30T12:00:00.000Z"}, {"title":"День рождения","date":"2017-04-18T12:00:00.000Z"} ] }`; расписание = JSON.parse(расписание, функция(ключ, значение) { if (key == 'date') вернуть новую дату (значение); возвращаемое значение; }); оповещение( Schedule.meetups[1].date.getDate() ); // работает!
JSON — это формат данных, имеющий собственный независимый стандарт и библиотеки для большинства языков программирования.
JSON поддерживает простые объекты, массивы, строки, числа, логические значения и null
.
JavaScript предоставляет методы JSON.stringify для сериализации в JSON и JSON.parse для чтения из JSON.
Оба метода поддерживают функции преобразователя для интеллектуального чтения/записи.
Если объект имеет toJSON
, он вызывается JSON.stringify
.
важность: 5
Превратите user
в JSON, а затем считайте его обратно в другую переменную.
пусть пользователь = { имя: «Джон Смит», возраст: 35 };
пусть пользователь = { имя: «Джон Смит», возраст: 35 }; пусть user2 = JSON.parse(JSON.stringify(user));
важность: 5
В простых случаях циклических ссылок мы можем исключить нарушающее свойство из сериализации по его имени.
Но иногда мы не можем просто использовать имя, поскольку оно может использоваться как в циклических ссылках, так и в обычных свойствах. Таким образом, мы можем проверить свойство по его значению.
Напишите функцию replacer
чтобы преобразовать все в строку, но удалите свойства, которые ссылаются на meetup
:
пусть комната = { номер: 23 }; пусть встреча = { название: «Конференция», оккупированныйBy: [{name: «Джон»}, {name: «Алиса»}], место: комната }; // циклические ссылки room.occupiedBy = встреча; meetup.self = встреча; alert( JSON.stringify(meetup, function replacer(key, value) { /* ваш код */ })); /* результат должен быть: { "title":"Конференция", "occupiedBy":[{"name":"Джон"},{"name":"Алиса"}], "место":{"номер":23} } */
пусть комната = { номер: 23 }; пусть встреча = { название: «Конференция», оккупированныйBy: [{name: «Джон»}, {name: «Алиса»}], место: комната }; room.occupiedBy = встреча; meetup.self = встреча; alert( JSON.stringify(meetup, function replacer(key, value) { return (ключ != "" && значение == встреча) ? неопределенное: значение; })); /* { "title":"Конференция", "occupiedBy":[{"name":"Джон"},{"name":"Алиса"}], "место":{"номер":23} } */
Здесь нам также нужно проверить key==""
для исключения первого вызова, где обычно value
— meetup
.