Pull to refresh

Comments 15

Как-то очень сложно, хотя и довольно симпатично…
Почему бы не определить у классов метод [toJSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior), который заменяет в ссылках указатели на строковые uuid, а при десериализации резолвить эти uuid обратно?

А как это решит проблему с сохранением информации о типах? К тому же придется везде писать свою реализацию toJSON и поддерживать ее в дальнейшем. В моем варианте ничего дописывать не надо.
Зачем вам типы? Есть какое то конкретное им применение или вам просто так привычно после c#?
Схема, создаваемая в редакторе на фронте, будет использоваться в бэке для обработки данных. Информация о типах пригодится для воссоздания классов на языке обработчика.

Другими словами, будет аналогичный десериализатор этой схемы на другом объектно-ориентированном языке.
По моему опыту, использовать сериализацию динамических типов, плохая идея, хотя бы потому, что зачем гонять туда сюда метадату о типах, а якобы гибкость в итоге обернется неоправданным усложнением кода и усложнением же его отладки. Если это все вам для самообучения, то отличная тема, но для продакшена лучше попробуйте github.com/google/flatbuffers или аналоги.

Несколько замечаний.


  1. А не лучше ли было вместо полного преобразования графа объектов использовать второй параметр в JSON.stringify?


  2. Вот это выражение — this._ctorToName[val.constructor] — полагается на то что toString для разных конструкторов будет выдавать разные значения. А это может быть и не так: одного неудачного декоратора достаточно чтобы половина "конструкторов" оказалась с одним и тем же toString… Надежнее использовать Map.


  3. Вместо __uuid лучше использовать символ. Мало ли кому еще придет в голову идея назначать __uuid объектам...


  4. Методы serialize и deserialize тоже лучше делать символами. Нельзя же так просто добавлять левые методы стандартным объектам.


  5. Метод deserialize должен быть статическим! Иначе получаются некрасивые костыли вроде того который вы показали для даты.

В итоге сериализацию даты я вижу как-то вот так:


Date.prototype[SerializerSymbols.serialize] = function () {
    return this.toISOString();
};

Date[SerializerSymbols.deserialize] = function (val) {
    return new Date(val);
};
Интересные замечания, спасибо. С символами раньше не работал, но обязательно включу их, если буду развивать проект.

Для моей текущей задачи даже имеющийся функционал избыточен, а интерфейс был дописан специально для статьи.
А ещё можно сначала записать массив узлов, а потом массив связей между ними.
Вон Oracle наоборот грозит из Java сериализацию выпилить.
… потому что десериализатор Java успешно находит любые классы по их полному имени. Тут же такой проблемы нет, поскольку используется белый список разрешенных конструкторов.
Статья интересная.

При этом представляется, что в подобном случае лучше разбить шаг «десериализация-связывание» на два. Т.е. мы на первом шаге просто десериализуем поступившие данные из DTO и лишь затем создаем бизнес-объекты. В этом случае links будет содержать id вершин, а вершины будут поступать в отдельном словаре вершин из которых мы будем доставать их по id.

Да, это чуть сложнее и требует работы как на стороне сервера (где мы преобразуем бизнес-объекты в DTO), так и на стороне клиента. Но выглядеть будет точно проще.

Была похожая задача, но решил просто: хотя у нас есть дерево, может даже с циклами, но оно же как-то представлено уже в памяти? Не на уровне "элемент циклически связан с другим", а на уровне "квадратик соединен стрелочкой". Вот это состояние и сериализуем бинарно. И обратно читаем. А уж определять связи, циклы и взаимоотношения — это задача алгоритмов поиска, анализа. Сериализация же для хранения и передачи, нефиг ей пастись в циклах )

Для таких задач (в смысле вообще работа со сложными графами объектов на клиенте, а не только сериализация) хорошо подходят normalizr + mobx-state-tree:


  • Преобразуем развесистое JSON-дерево от сервера в плоские "таблицы" с помощью normalizr
  • Скармливаем их mobx-state-tree в качестве снапшота состояния
  • Ну а обратно сериализовать можно совсем просто:

function serialize(data) {
  const visited = new Set();
  return JSON.stringify(data, (key, value) => {
    if (isObject(value) && isInteger(value.id)) {
      if (visited.has(value)) {
        return { id: value.id };
      }
      visited.add(value);
      return value;
    }
    return value;
  });
}

Плюсы:


  • Где удобно, можно работать с данными как с таблицей (например применить снапшот или смерджить)
  • Где удобно — как с "живыми" объектами (добавить/убрать связи, изменить поля)
  • Автоматический рендеринг изменений (за счет MobX)
  • Проверка типов данных (в том числе в runtime)

Минусы:


  • Нужно прописывать схемы данных на клиенте (но мы ведь все равно так или иначе это сделаем)

Я бы рекомендовал сериализовать во что-то типа такого:


{
    "user": {
        "jin": {
            "name": "Jin",
            "karma": 100500,
            "birthday": "1984-08-04",
            "articles": [ "foo" ]
        }
    },
    "bot": {
        "google": {
            "name": "GoogleBot",
            "articles": [ "bar" ]
        }
    },
    "article": {
        "foo": {
            "name": "Fooooo",
            "author": [ "user" , "jin" ]
        },
        "bar": {
            "name": "Barrrr",
            "authors": [ "bot" , "google" ]
        }
    }
}

По уровням:


  • тип сущности
  • идентификатор сущности
  • данные сущности

Данные могут быть:


  • число: 100500
  • строка: "GoogleBot"
  • момент времени: "1894-08-04"
  • типизированная ссылка на сущность (только идентификатор, ведь тип заранее известен): "foo"
  • полиморфная ссылка на сущность (имя типа и идентификатор, когда ссылаться может на разные типы): [ "user" , "jin" ]
  • список типизированных ссылок: [ "foo" , "bar" ]
  • список полиморфных ссылок: [ [ "user" , "jin" ] , [ "bot" , "google" ] ]
  • словарь (ключи — строки, значения — любые типы данных): { "foo": 1 , "bar": 2 }

Преимущества:


  • простота и ясность
  • скорость обработки (не надо парсить ключи и тп)
  • нормализация (нет дублирования данных)
  • компактность (не храним тип, если он и так известен)
  • возможность ссылаться на любые сущности, в том числе на внешние (не представленные в конкретном срезе графа)
  • совместимость со статически типизированными языками (данные разных типов лежат в разных "букетах")
UFO just landed and posted this here
Sign up to leave a comment.

Articles