Формат JSON, метод toJSON
Допустим, у нас есть сложный объект, и мы хотели бы преобразовать его в строку, чтобы отправить по сети или просто вывести для логирования.
Естественно, такая строка должна включать в себя все важные свойства.
Мы могли бы реализовать преобразование следующим образом:
let user = < name: "John", age: 30, toString() < return `", age: $>`; > >; alert(user); //
…Но в процессе разработки добавляются новые свойства, старые свойства переименовываются и удаляются. Обновление такого toString каждый раз может стать проблемой. Мы могли бы попытаться перебрать свойства в нём, но что, если объект сложный, и в его свойствах имеются вложенные объекты? Мы должны были бы осуществить их преобразование тоже.
К счастью, нет необходимости писать код для обработки всего этого. У задачи есть простое решение.
JSON.stringify
JSON (JavaScript Object Notation) – это общий формат для представления значений и объектов. Его описание задокументировано в стандарте RFC 4627. Первоначально он был создан для JavaScript, но многие другие языки также имеют библиотеки, которые могут работать с ним. Таким образом, JSON легко использовать для обмена данными, когда клиент использует JavaScript, а сервер написан на Ruby/PHP/Java или любом другом языке.
JavaScript предоставляет методы:
- JSON.stringify для преобразования объектов в JSON.
- JSON.parse для преобразования JSON обратно в объект.
Например, здесь мы преобразуем через JSON.stringify данные студента:
let student = < name: 'John', age: 30, isAdmin: false, courses: ['html', 'css', 'js'], wife: null >; let json = JSON.stringify(student); alert(typeof json); // мы получили строку! alert(json); /* выведет объект в формате JSON: < "name": "John", "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], "wife": null >*/
Метод JSON.stringify(student) берёт объект и преобразует его в строку.
Полученная строка json называется JSON-форматированным или сериализованным объектом. Мы можем отправить его по сети или поместить в обычное хранилище данных.
Обратите внимание, что объект в формате JSON имеет несколько важных отличий от объектного литерала:
- Строки используют двойные кавычки. Никаких одинарных кавычек или обратных кавычек в JSON. Так ‘John’ становится «John» .
- Имена свойств объекта также заключаются в двойные кавычки. Это обязательно. Так age:30 становится «age»:30 .
JSON.stringify может быть применён и к примитивам.
JSON поддерживает следующие типы данных:
- Объекты
- Массивы [ . ]
- Примитивы:
- строки,
- числа,
- логические значения true/false ,
- null .
// число в JSON остаётся числом alert( JSON.stringify(1) ) // 1 // строка в JSON по-прежнему остаётся строкой, но в двойных кавычках alert( JSON.stringify('test') ) // "test" alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON является независимой от языка спецификацией для данных, поэтому JSON.stringify пропускает некоторые специфические свойства объектов JavaScript.
- Свойства-функции (методы).
- Символьные ключи и значения.
- Свойства, содержащие undefined .
let user = < sayHi() < // будет пропущено alert("Hello"); >, [Symbol("id")]: 123, // также будет пропущено something: undefined // как и это - пропущено >; alert( JSON.stringify(user) ); // <> (пустой объект)
Обычно это нормально. Если это не то, чего мы хотим, то скоро мы увидим, как можно настроить этот процесс.
Самое замечательное, что вложенные объекты поддерживаются и конвертируются автоматически.
let meetup = < title: "Conference", room: < number: 23, participants: ["john", "ann"] >>; alert( JSON.stringify(meetup) ); /* вся структура преобразована в строку: < "title":"Conference", "room":, > */
Важное ограничение: не должно быть циклических ссылок.
let room = < number: 23 >; let meetup = < title: "Conference", participants: ["john", "ann"] >; meetup.place = room; // meetup ссылается на room room.occupiedBy = meetup; // room ссылается на meetup JSON.stringify(meetup); // Ошибка: Преобразование цикличной структуры в JSON
Здесь преобразование завершается неудачно из-за циклической ссылки: room.occupiedBy ссылается на meetup , и meetup.place ссылается на room :
Исключаем и преобразуем: replacer
Полный синтаксис JSON.stringify :
let json = JSON.stringify(value, [replacer, space])
value Значение для кодирования. replacer Массив свойств для кодирования или функция соответствия function(key, value) . space Дополнительное пространство (отступы), используемое для форматирования.
В большинстве случаев JSON.stringify используется только с первым аргументом. Но если нам нужно настроить процесс замены, например, отфильтровать циклические ссылки, то можно использовать второй аргумент JSON.stringify .
Если мы передадим ему массив свойств, будут закодированы только эти свойства.
let room = < number: 23 >; let meetup = < title: "Conference", participants: [, ], place: room // meetup ссылается на room >; room.occupiedBy = meetup; // room ссылается на meetup alert( JSON.stringify(meetup, ['title', 'participants']) ); // <"title":"Conference","participants":[<>,<>]>
Здесь мы, наверное, слишком строги. Список свойств применяется ко всей структуре объекта. Так что внутри participants – пустые объекты, потому что name нет в списке.
Давайте включим в список все свойства, кроме room.occupiedBy , из-за которого появляется цикличная ссылка:
let room = < number: 23 >; let meetup = < title: "Conference", participants: [, ], place: room // meetup ссылается на room >; room.occupiedBy = meetup; // room ссылается на meetup alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) ); /* < "title":"Conference", "participants":[,], "place": > */
Теперь всё, кроме occupiedBy , сериализовано. Но список свойств довольно длинный.
К счастью, в качестве replacer мы можем использовать функцию, а не массив.
Функция будет вызываться для каждой пары (key, value) , и она должна возвращать заменённое значение, которое будет использоваться вместо исходного. Или undefined , чтобы пропустить значение.
В нашем случае мы можем вернуть value «как есть» для всего, кроме occupiedBy . Чтобы игнорировать occupiedBy , код ниже возвращает undefined :
let room = < number: 23 >; let meetup = < title: "Conference", participants: [, ], place: room // meetup ссылается на room >; room.occupiedBy = meetup; // room ссылается на meetup alert( JSON.stringify(meetup, function replacer(key, value) < alert(`$: $`); return (key == 'occupiedBy') ? undefined : value; >)); /* пары ключ:значение, которые приходят в replacer: : [object Object] title: Conference participants: [object Object],[object Object] 0: [object Object] name: John 1: [object Object] name: Alice place: [object Object] number: 23 occupiedBy: [object Object] */
Обратите внимание, что функция replacer получает каждую пару ключ/значение, включая вложенные объекты и элементы массива. И она применяется рекурсивно. Значение this внутри replacer – это объект, который содержит текущее свойство.
Первый вызов – особенный. Ему передаётся специальный «объект-обёртка»: . Другими словами, первая (key, value) пара имеет пустой ключ, а значением является целевой объект в общем. Вот почему первая строка из примера выше будет «:[object Object]» .
Идея состоит в том, чтобы дать как можно больше возможностей replacer – у него есть возможность проанализировать и заменить/пропустить даже весь объект целиком, если это необходимо.
Форматирование: space
Третий аргумент в JSON.stringify(value, replacer, space) – это количество пробелов, используемых для удобного форматирования.
Ранее все JSON-форматированные объекты не имели отступов и лишних пробелов. Это нормально, если мы хотим отправить объект по сети. Аргумент space используется исключительно для вывода в удобочитаемом виде.
Ниже space = 2 указывает JavaScript отображать вложенные объекты в несколько строк с отступом в 2 пробела внутри объекта:
let user = < name: "John", age: 25, roles: < isAdmin: false, isEditor: true >>; alert(JSON.stringify(user, null, 2)); /* отступ в 2 пробела: < "name": "John", "age": 25, "roles": < "isAdmin": false, "isEditor": true >> */ /* для JSON.stringify(user, null, 4) результат содержит больше отступов: < "name": "John", "age": 25, "roles": < "isAdmin": false, "isEditor": true >> */
Третьим аргументом также может быть строка. В этом случае строка будет использоваться для отступа вместо ряда пробелов.
Параметр space применяется исключительно для логирования и красивого вывода.
Пользовательский «toJSON»
Как и toString для преобразования строк, объект может предоставлять метод toJSON для преобразования в JSON. JSON.stringify автоматически вызывает его, если он есть.
let room = < number: 23 >; let meetup = < title: "Conference", date: new Date(Date.UTC(2017, 0, 1)), room >; alert( JSON.stringify(meetup) ); /* < "title":"Conference", "date":"2017-01-01T00:00:00.000Z", // (1) "room": // (2) > */
Как видим, date (1) стал строкой. Это потому, что все объекты типа Date имеют встроенный метод toJSON , который возвращает такую строку.
Теперь давайте добавим собственную реализацию метода toJSON в наш объект room (2) :
let room = < number: 23, toJSON() < return this.number; >>; let meetup = < title: "Conference", room >; alert( JSON.stringify(room) ); // 23 alert( JSON.stringify(meetup) ); /* < "title":"Conference", "room": 23 >*/
Как видите, toJSON используется как при прямом вызове JSON.stringify(room) , так и когда room вложен в другой сериализуемый объект.
JSON.parse
Чтобы декодировать JSON-строку, нам нужен другой метод с именем JSON.parse.
let value = JSON.parse(str, [reviver]);
str JSON для преобразования в объект. reviver Необязательная функция, которая будет вызываться для каждой пары (ключ, значение) и может преобразовывать значение.
// строковый массив let numbers = "[0, 1, 2, 3]"; numbers = JSON.parse(numbers); alert( numbers[1] ); // 1
Или для вложенных объектов:
let user = '< "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] >'; user = JSON.parse(user); alert( user.friends[1] ); // 1
JSON может быть настолько сложным, насколько это необходимо, объекты и массивы могут включать другие объекты и массивы. Но они должны быть в том же JSON-формате.
Вот типичные ошибки в написанном от руки JSON (иногда приходится писать его для отладки):
let json = `< name: "John", // Ошибка: имя свойства без кавычек "surname": 'Smith', // Ошибка: одинарные кавычки в значении (должны быть двойными) 'isAdmin': false, // Ошибка: одинарные кавычки в ключе (должны быть двойными) "birthday": new Date(2000, 2, 3), // Ошибка: не допускается конструктор "new", только значения "gender": "male" // Ошибка: отсутствует запятая после непоследнего свойства "friends": [0,1,2,3], // Ошибка: не должно быть запятой после последнего свойства >`;
Кроме того, JSON не поддерживает комментарии. Добавление комментария в JSON делает его недействительным.
Существует ещё один формат JSON5, который поддерживает ключи без кавычек, комментарии и т.д. Но это самостоятельная библиотека, а не спецификация языка.
Обычный JSON настолько строг не потому, что его разработчики ленивы, а потому, что позволяет легко, надёжно и очень быстро реализовывать алгоритм кодирования и чтения.
Использование reviver
Представьте, что мы получили объект meetup с сервера в виде строки данных.
// title: (meetup title), date: (meetup date) let str = '';
…А теперь нам нужно десериализовать её, т.е. снова превратить в объект JavaScript.
Давайте сделаем это, вызвав JSON.parse :
let str = ''; let meetup = JSON.parse(str); alert( meetup.date.getDate() ); // Ошибка!
Значением meetup.date является строка, а не Date объект. Как JSON.parse мог знать, что он должен был преобразовать эту строку в Date ?
Давайте передадим JSON.parse функцию восстановления вторым аргументом, которая возвращает все значения «как есть», но date станет Date :
let str = ''; let meetup = JSON.parse(str, function(key, value) < if (key == 'date') return new Date(value); return value; >); alert( meetup.date.getDate() ); // 30 - теперь работает!
Кстати, это работает и для вложенных объектов:
let schedule = `< "meetups": [ , ] >`; schedule = JSON.parse(schedule, function(key, value) < if (key == 'date') return new Date(value); return value; >); alert( schedule.meetups[1].date.getDate() ); // 18 - отлично!
Итого
- JSON – это формат данных, который имеет собственный независимый стандарт и библиотеки для большинства языков программирования.
- JSON поддерживает простые объекты, массивы, строки, числа, логические значения и null .
- JavaScript предоставляет методы JSON.stringify для сериализации в JSON и JSON.parse для чтения из JSON.
- Оба метода поддерживают функции преобразования для интеллектуального чтения/записи.
- Если объект имеет метод toJSON , то он вызывается через JSON.stringify .
Задачи
Преобразуйте объект в JSON, а затем обратно в обычный объект
важность: 5
Преобразуйте user в JSON, затем прочитайте этот JSON в другую переменную.
let user = < name: "Василий Иванович", age: 35 >;
JSON — Типы данных
В JSON значения могут быть следующих типов:
Значения JSON не могут быть следующих типов:
Строки JSON
Строки в JSON должны записываться в двойных кавычках.
Числа JSON
Числа в JSON должны быть целочисленными или с плавающей точкой.
Объекты JSON
Значения в JSON могут быть объектами.
Объекты как значения в JSON должны следовать тем же правилам, что и объекты JSON.
Массивы JSON
Значения в JSON могут быть массивами.
Логические значения в JSON (boolean)
В JSON могут определяться логически значения true/false.
Значение null
В JSON могут определяться значения null.
Использование JSON в JavaScript и Node.js
JSON — это один из самых популярных форматов обмена данными между бекэндом и фронтэндом. Еще JSON известен как JavaScript Object Notation. Он очень похож на то, как выглядят обычные JavaScript объекты, но также имеет свои особенности. Читается — “джейсон”, хотя часть твоих будущих коллег будут говорить и “джсон”, и “жсон”, и даже “жисон”.
JSON не накладывает никаких ограничений на язык программирования, который будет с ним работать. Ты можешь работать в организации, где часть бекэнд сервисов написана на Python, часть на Java, а фронт на JS и все они прекрасно обмениваются JSON сообщениями.
Хранение данных в формате JSON
Начнем с того, что JSON — это строка. Это позволяет при необходимости очень эффективно сжимать данные. Недостаток — мы не можем хранить циклические структуры данных, например объект, который ссылается на самого себя.
(Почти) все должно быть обернуто в кавычки
В отличие от JavaScript, ты должен пользоваться только двойными кавычками и оборачивать в них все свойства объектов. Одинарные или обратные (косые) кавычки использовать нельзя.
В JS у нас был такой объект
< name: 'Jack', isMarried: false, age: 25, >
А в JSON он станет таким
< "name": "Jack", "isMarried": false, "age": 25 >
Обрати внимание, что в JavaScript объектах наличие запятой после age: 25, является допустимым, а в JSON — нет.
Названия всех полей обернуты в двойные кавычки, а значения — не все. Числа и булевы значения хранятся без кавычек.
Объекты хранятся в фигурных скобках
Для хранения объектов используются фигурные скобки, как и в JS.
Заметь, что если сервер отвечает в формате JSON, то предполагается, что он ответит объектом. Ты не можешь просто список полей. Они все обязательно должны быть обернуты в фигурные скобки, чтобы стать JSON объектом.
Массивы хранятся в квадратных скобках
Все точно как в JS, оборачиваем название массива в двойные кавычки, а сам массив указываем в квадратных скобках.
< "pets": ["Rex", "Sandy"] >
Еще раз обращаем внимание, что в конце строки нет ни запятой, ни точки с запятой.
Все данные JSON в объекте хранятся как пары “ключ”:“значение”
Как и в JS, ты можешь добавлять в объект только пары ключ:значение . Если тебе нужно сохранить несколько значений без ключей, то тебе нужен массив.
Конвертация JavaScript объектов в JSON и обратно
Для конвертации из обычного JS объекта в JSON строку, тебе нужна функция JSON.stringify(obj) . Она доступна без установки дополнительных модулей. Передаешь ей объект obj и на выходе получаешь JSON объект.
const user = < name: 'Jack', isMarried: false, age: 25, > const userJSON = JSON.stringify(user); console.log(userJSON); //
Для конвертации из JSON в обычный объект, нам нужна функция JSON.parse(s) . Даем строку в формате JSON на вход, получаем JS объект на выходе.
const jsonString = ''; const parsedUser = JSON.parse(jsonString); console.log(parsedUser); //
Express.js и JSON
Так как мы знаем, что JSON объект — это строка, нам будет просто модифицировать сервер и вместо Hello, Express.js отправлять какой-нибудь объект.
Представим, что нам нужно передать на фронтэнд объект
< name: 'Hero', isLearning: true, level: 'apprentice', >
Сделаем это несколькими способами. Во всех случаях фронтэнд получит одно и то же, в чем ты можешь убедиться с помощью запроса в браузере.
server.get('/', (req, res) => < return res.send(''); >)
server.get('/', (req, res) => < const user = < name: 'Hero', isLearning: true, level: 'apprentice' >; return res.send(JSON.stringify(user)); >)
server.get('/', (req, res) => < const user = < name: 'Hero', isLearning: true, level: 'apprentice' >; return res.json(user); >)
Повторю еще раз. Во всех случаях в итоге получится одно и то же. Мы отправим ответ со статусом 200 и строкой , которую получатель сможет использовать как ему захочется.
По правде говоря, между res.send и res.json есть разница и она состоит в типе ответа. Это специальный заголовок Content-Type, который в случае res.send устанавливается равным text/html , а для res.json — application/json .
Используй res.json , если у тебя есть готовый объект, который ты хочешь отправить в формате JSON.
Третий пример самый удачный, так как нам нужно делать миниум лишних действий. Мы передаем объект в res.json и преобразование в JSON строку происходит внутри. Дополнительный (явный) вызов JSON.stringify , как в примере 2, в этом случае не нужен.
Что такое JSON простыми словами
Многим приложениям нужно обмениваться данными между клиентом и сервером.
Долгое время эталонным форматом данных для обмена информацией между двумя объектами считался XML. Затем, в начале 2000-х, появился альтернативный формат JSON.
В данной статье вы узнаете все о JSON. Мы рассмотрим, что это, и как им пользоваться, а также разберем ряд популярных заблуждений.
Что такое JSON?
JSON (JavaScript Object Notation, нотация объектов JavaScript) — это текстовый формат обмена данными. Он представлен наборами пар «ключ-значение», причем ключ — это всегда строка, а значение может задаваться одним из следующих типов:
- число;
- строка;
- логическое значение;
- массив;
- объект;
- нулевое значение null.
Несколько важных правил:
- В формате данных JSON ключи прописываются в двойных кавычках.
- Ключ и значение разделяются двоеточием (:).
- Может быть несколько пар «ключ-значение». Каждая пара отделяется запятой (,).
- В данных JSON недопустимы комментарии (// или /* */). (Но при желании это ограничение можно обойти)
Ниже приведен пример простых данных в JSON:
Допустимые данные в JSON возможны в 2 разных форматах:
- Набор пар «ключ-значение» в фигурных скобках . Это показано в примере выше.
- Упорядоченные списки пар «ключ-значение», разделенных запятой (,) и заключенных в квадратные скобки [. ] . См. пример ниже:
Предположим, вы уже писали что-то на JavaScript. Тогда у вы можете ошибочно предположить, что формат JSON и объекты JavaScript (и массивы объектов) очень похожи. Но это не так. Чуть позже мы подробно об этом поговорим.
Структура JSON разработана на основе синтаксиса объектов JavaScript, и это единственное, что объединяет JSON и объекты JavaScript.
Формат JSON не зависит от языка программирования. Мы можем использовать JSON в Python, Java, PHP и многих других языках.
Примеры формата данных JSON
Сохранять данные JSON можно в файле с расширением .json . Давайте создадим файл employee.json с атрибутами сотрудника. Они представлены в виде ключей и значений.
< "name": "Aleix Melon", "id": "E00245", "role": ["Dev", "DBA"], "age": 23, "doj": "11-12-2019", "married": false, "address": < "street": "32, Laham St.", "city": "Innsbruck", "country": "Austria" >, "referred-by": "E0012" >
В примере выше присутствуют следующие атрибуты сотрудника:
- name – имя сотрудника. Значение в строковом формате (String). Оно указано в двойных кавычках.
- id – уникальный идентификатор сотрудника. Опять же, в строковом формате.
- role – роли, которые сотрудник выполняет в организации. Таких ролей может быть несколько, поэтому лучше перечислять эти данные в формате массива (Array).
- age – текущий возраст сотрудника. Это числовое значение (Number).
- doj – дата найма сотрудника. Поскольку это дата, ее добавляют в двойных кавычках и обрабатывают как строку.
- married – замужем/женат ли сотрудник? Ответом может быть да/нет (то есть true или false ), так что это логический формат (Boolean).
- address – адрес сотрудника. Может состоять из нескольких частей: улица, город, страна, индекс и т.д. Такое поле лучше представлять в виде объекта (Object с парами «ключ-значение»).
- referred-by – идентификатор сотрудника, который порекомендовал этого человека на должность в организацию. Если сотрудник пришел по рекомендации, то атрибут имеет значение. В остальных случаях поле остается пустым, т.е. null .
Теперь давайте создадим набор данных по сотрудникам в формате JSON. Если мы хотим добавить несколько записей о разных сотрудниках, то необходимо прописать их в квадратных скобках [. ] .
[ < "name": "Aleix Melon", "id": "E00245", "role": ["Dev", "DBA"], "age": 23, "doj": "11-12-2019", "married": false, "address": < "street": "32, Laham St.", "city": "Innsbruck", "country": "Austria" >, "referred-by": "E0012" >, < "name": "Bob Washington", "id": "E01245", "role": ["HR"], "age": 43, "doj": "10-06-2010", "married": true, "address": < "street": "45, Abraham Lane.", "city": "Washington", "country": "USA" >, "referred-by": null > ]
Обратите внимание на значение атрибута referred-by для сотрудника Боба Вашингтона (Bob Washington). Оно пустое. То есть никто из сотрудников не давал ему рекомендаций.
Как использовать данные JSON в качестве значения строки
Мы узнали, как форматировать данные внутри файла JSON. Еще можно использовать данные JSON в качестве строковых значений и присваивать их переменной. Поскольку JSON считается текстовым форматом, в большинстве языков программирования его можно обрабатывать как строку.
Давайте рассмотрим пример, как это делается JavaScript. Вы можете добавить данные JSON в одну строку. Перечисление делается через одинарные кавычки ‘. ‘ .
const user = '';
Если вы хотите сохранить форматирование, то данные JSON лучше создавать с помощью шаблонных литералов (template literals).
const user = `< "name": "Alex C", "age": 2, "city": "Houston" >`;
Кроме того, это очень удобное решение, если нужно создать данные JSON с динамическими значениями.
const age = 2; const user = `< "name": "Alex C", "age": $, "city": "Houston" >`; console.log(user); // Output
Объекты JavaScript и JSON – это НЕ одно и то же
Формат данных JSON создавался на базе объектной структуры JavaScript. Но все сходства на этом заканчиваются.
Объекты в JavaScript:
- у объектов JavaScript могут быть методы, а у JSON – нет;
- ключи можно добавлять без кавычек;
- разрешены комментарии;
- отдельные сущности
Как преобразовать JSON в объект JavaScript и наоборот
В JavaScript есть 2 встроенных метода по преобразованию данных JSON в объекты JavaScript и наоборот.
Как преобразовать данные JSON в объект JavaScript
Для преобразования данных JSON в объект JavaScript используется метод JSON.parse() . Он проводит синтаксический разбор (парсинг) допустимой строки JSON в объект JavaScript.
const userJSONData = `< "name": "Alex C", "age": 2, "city": "Houston" >`; const userObj = JSON.parse(userJSONData); console.log(userObj);
Как преобразовать объект JavaScript в данные JSON
Для преобразования объекта JavaScript в данные JSON используется метод JSON.stringify() .
const userObj = < name: 'Alex C', age: 2, city: 'Houston' >const userJSONData = JSON.stringify(userObj); console.log(userJSONData);
Должно быть, вы обратили внимание на слово JSON , которое используется для вызова методов parse() и stringify() . Это встроенный объект JavaScript, который, хоть и называется JSON (хотя с тем же успехом он мог бы называться JSONUtil), но не имеет никакого отношения к формату JSON. Так что, пожалуйста, помните об этом.
Как обрабатывать ошибки «Unexpected token u in JSON at position 1» и другие?
При обработке JSON могут возникать ошибки. Это нормально. Например, при разборе данных JSON в объект JavaScript вдруг появляется следующее сообщение:
Если возникает такая ошибка, обязательно проверьте корректность ваших данных в JSON. Чаще всего причина синтаксического сбоя кроется в небольшой ошибке, которую вы случайно сделали в исходных данных JSON. Проверить правильность данных и форматов JSON можно с помощью JSON Linter.