В современных веб-приложениях нам приходится иметь дело с необработанными объектами, полученными из 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

Развлекайся ! а если желаете доработок, просто скажите мне!