В современных веб-приложениях нам приходится иметь дело с необработанными объектами, полученными из HTTP-вызова или из кеш-хранилища (например, localStorage). Каждый раз, когда нам приходится иметь дело с этим, мы используем JSON API следующим образом:
let restoredObject = JSON.parse(StringFromApiCall)
Но когда мы это делаем, у нас нет «реальных сущностей», это просто необработанный объект без каких-либо методов предметной области.
Представьте себе приложение, в котором вы имеете дело с книгами, авторами, редакторами, сериями… В этом приложении вы написали Такой класс сущности:
class Book { constructor(newTitle) { let title = newTitle const editors = [] const authors = [] let serie this.getTitle = () => { return title } this.setTitle = newTitle => { title = title } this.addAuthor = author => { if (typeof author !== undefined) { throw new Error('author must be an instance of Author') } authors.push(author) } this.addEditor = editor => { if (typeof editor!== undefined) { throw new Error('editor must be an instance of Author') } editors.push(editor) } } } // class Author and Editor may also have different method to restore date string into Date object ...
Когда вы получаете книгу из HTTP-вызова, вы восстанавливаете объект с помощью JSON.parse, но у вас не будет доступа ни к методам класса Book, ни к методам из класса Authors или Editors. Итак, как мы могли это сделать?
JSON.parse принимает 2 параметра, первый - это строка json, а второй - это обратный вызов, который будет выполняться на каждом узле объекта для восстановления. Он пойдет как можно глубже, а затем применит обратный вызов. Было бы полезно восстановить дату по метке времени, если вы считаете, что каждое свойство, которое заканчивается на «Дата» (например, Дата публикации, Дата создания…), является меткой времени. В этом случае обратный вызов может проверить свойство и вернуть новую дату (значение):
const objectToRestore = '{"title": "me & you", "startDate": 1523009092661}' const reviver = (key, value) => { if (key.endsWith('Date')) { return new Date(value) } return value } const objectRevived = JSON.parse(objectToRestore, reviver) // {title: "me & you", startDate: Fri Apr 06 2018 12:04:52 GMT+0200 (Paris, Madrid (heure d’été))}
Это довольно крутая функция, но когда у вас есть вложенный объект, очень часто у вас будет одно и то же имя свойства, поэтому трудно определить, где вы находитесь в дереве объектов. Более того, оживитель вызывается один раз для каждого узла, он не знает, что уже было обработано ... Так что сложно попытаться восстановить реальные объекты с помощью этого инструмента.
Вот почему я решил разработать шаблон, который позволит восстанавливать мои сущности, даже если они вложены. Я уже делал это для PHP с Symfony 4, когда хотел преобразовать данные json, полученные контроллерами, в реальную сущность (подробнее здесь: https://medium.com/@rebolon/how-to-bind-your-favorite-js -framework-with-symfony-4-8c9ba86e2b8d , а репозиторий github находится здесь: https://github.com/Rebolon/ApiJsonParamConverterComponent ).
Так что мне просто пришлось перекодировать это в javascript!
Для каждой из ваших сущностей вы должны написать Reviver. Этот будет описывать сущность:
* имя узла, представляющего сущность и используемого в строке json: для нашей книги это будет 'book'
* экземпляр класса сущности it будет управлять: для нашей книги это будет new Book ()
* список собственных свойств: для нашей книги это будет ['title',]
* список ее свойств, которые содержат связанные сущность и некоторая информация, которая поможет Reviver восстановить эти сущности: для нашей книги это будет ['авторы': {'setter': () = ›{}, 'reviver': () =› {}, 'cb ': () = ›{},' Editors ': {…},]
Как описать связанные сущности:
Для отношения типа «Многие ко многим»:
* сеттер: это метод (или имя свойства), вызываемый родительским элементом для связывания дочернего объекта.
* reviver: это экземпляр reviver для связанного экземпляра.
* cb: это обратный вызов, который будет использоваться для привязки дочернего элемента к родительскому.
Для такого отношения, как OneToMany:
* reviver: это экземпляр reviver для связанного экземпляра.
* registryKey: это имя ключа кеша, используемого для дедупликации объекта (когда вы получаете 2 под-объекта, которые имеют все одно и то же свойство, он не создаст 2 экземпляра, а повторно использует первый созданный).
Вот полный образец книги:
import {ItemAbstractReviver, Accessor} from "@rebolon/json-reviver";
class BookReviver extends ItemAbstractReviver
{
constructor (
editorsReviver,
serieReviver
) {
super()
this.editorsReviver = editorsReviver
this.serieReviver = serieReviver
}
// The name of the node in the json string/object
getNodeName() {
return 'book'
}
// The entity for which the reviver works
getNewEntity() {
return new Book()
}
// List of props (int, string, date, bool) of the entity
public getEzPropsName()
{
return ['title', ]
}
// List of props that links to other entities (relations Many To Many)
// And configuration of how to restore them
public getManyRelPropsName()
{
return {
'editors': {
'reviver': this.editorsReviver,
'setter': 'addEdition',
'cb': function (relation, entity) {
Accessor('book', relation, entity)
},
},
... // do the same for authors
}
}
// List of props that links to other entities (relations Many To One)
public getOneRelPropsName()
{
return {
'serie': {
'reviver': this.serieReviver,
'registryKey': 'serie',
},
}
}
}
Код пакета написан с помощью Typescript, но вы можете использовать его в проекте javascript.
Для установки пакета npm install @rebolon/json-reviver
Репозиторий находится здесь: https://github.com/Rebolon/json-reviver
Развлекайся ! а если желаете доработок, просто скажите мне!