Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snapshot (de)serialization of normalized stores with models containing polymorphic recursive refs #493

Open
Tim-Kerr opened this issue Dec 29, 2022 · 0 comments

Comments

@Tim-Kerr
Copy link

Tim-Kerr commented Dec 29, 2022

Hi, thanks for the great mobx-keystone library! I've run into what I believe might be an edge use case that might not be supported by mobx-keystone and wanted some feedback on the issue.

I have an application that implements a basic file system using mobx-keystone that gets persisted in local storage as a json string. This file system is a recursive tree-like structure of folders and files. While it would be natural to store this data in the mobx-keystone root tree in its nested form, I'd really like the store to be normalized backed by an ObjectMap and use Refs to resolve the parent / child relationships of the folders and files. However when I try to deserialize the normalized store of folders I run into an error when trying to resolve the refs.

Calling fromSnapshot<FileStore>(JSON.parse(storeJsonString)) results in error:

Uncaught Error: a reference of type '...' could not resolve an object with id ...

I assume this is because fromSnapshot is trying to deserialize the normalized collection, but the items in the collection contain references to other items in the same collection currently being deserialized.

Here's what my models look like at a high level (sorry if all the abstractions make it difficult to follow):

/**
 * Represents an abstract normalized store
 */
@model('data-store')
export class DataStore<TDataEntity extends IDataEntity> extends Model(<TDataEntity>() => ({
  id: idProp,
  _store: prop(() => objectMap<TDataEntity>())
}))<TDataEntity> implements IDataStore<TDataEntity> {
  get(id: string) {
    return this._store.get(id);
  }

  @modelAction
  set(entity: TDataEntity) {
    this._store.set(entity.id, entity);
    return entity;
  }

  @modelAction
  delete(id: string) {
    this._store.delete(id);
  }

  @computed
  get list() {
    return Array.from(this._store.values());
  }

  @computed
  get size() {
    return this._store.size
  }
}
/**
 * Represents a normalized store of file system nodes
 */
@model('file-store')
export class FileStore extends ExtendedModel(
  modelClass<DataStore<IFilesystemNode>>(DataStore), {}
) {
  onAttachedToRootStore() {
    // On snapshot, persist the contents of the file store to local storage.
    const dispose = onSnapshot<FileStore>(this, (snapshot) => {
      localStorage.setItem(FILE_STORE_KEY, JSON.stringify(snapshot));
    });

    return () => dispose();
  }
}
/**
 * Represents an abstract base node in a filesystem
 */

@model('filesystem-node')
export class FilesystemNode extends Model({
  id: idProp,
  filename: prop<string>('').withSetter(),
}) {
  @observable
  nodeType = FilesystemNodeType.None;
}
/**
 * Represents a folder in a filesystem
 */
@model('folder')
export class Folder extends ExtendedModel(FilesystemNode, {
  children: prop<Ref<FilesystemNode>[]>(() => []).withSetter()
}) {
  readonly nodeType = FilesystemNodeType.Folder;

  @modelAction
  addChild(child: Ref<IFilesystemNode>) {
    this.children.push(child);
  }

  @modelAction
  removeChild(id: string) {
    this.children = this.children.filter(child => child.id !== id);
  }

My question: Is there a way to deserialize a model's snapshot JSON when it contains Refs to other objects in the same collection? Please let me know if my question is unclear. Thanks again for the great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant