toJson method - why doesn't it recursively stringify all sub shared types?

Hi all :hatching_chick:

I’m wondering why calling sharedType.toJSON() doesn’t recurse through all children and call toJSON() on all child shared types. Any insight into this @dmonad?

Use cases include:

  • debugging / inspecting document state (a la Liveblocks Yjs dev tools)
  • Providing “read only” access to a document edited by yjs
  • Data analysis / querying documents
  • Syncing to a store for use in React (? not 100% this makes sense)

Here is some pseudocode that I am thinking of giving a try:

// pseudo code
getJsonFromYType( sharedType: YDoc or YType ):
  const doc = {};
  for child of sharedType:
    let key, val of child;
    if val is a Y.Type:
      doc[key] = getJsonFromYType( val )
    else:
      doc[key] = JSON.stringify(val)
  return doc

I’m going to try this locally, but I would be open to creating a PR to yjs with a version of this if it would be useful. I imagine this has been discussed in the past although I didn’t see any Github issues or forum discussions on the subject. Maybe the thinking is that a json representation of a yjs document is “dead” and not useful?

Thanks in advance for any thoughts on the above.

1 Like

toJson on a shared type does recurse nested shared types for me:

import * as Y from 'yjs'

const doc = new Y.Doc()
const root = doc.getMap('root')
const map1 = new Y.Map()
const map2 = new Y.Map()
root.set('map1', map1)
map1.set('map2', map2)
map2.set('foo', 'bar')

console.log(root.toJSON())
// { map1: { map2: { foo: 'bar' } } }

Demo: View in CodeSandbox

1 Like

Thank you so much for the response @raine !

So I’m seeing that doc.toJSON() is what I was calling and that does not go through the whole document, AND I’m seeing that that method is marked as deprecated. So that’s totally on me.

Thanks again for the gracious response.

Ok just because I felt silly for even asking this question I dug slightly deeper.

I was able to print the full document on updates using the following code:

    yDoc.on("update", () => {
      const json = {};

      yDoc.share.forEach((value, key) => {
        json[key] = value.toJSON();
      });

      console.log(json);
    });

I’m not sure why this is actually different than the Doc.toJSON() method, although it seems to actually properly call toJSON on all the sub shared types of the document.

Here’s a link to the code for the Doc.toJSON method:

Here’s a link to the code for the Y.Array.toJSON method:

Great, I’m glad you figured some stuff out. That is strange about doc.toJSON(). The only thing I can think of is this strange behavior where doc.toJSON() only works when the getter has been first called for each top-level type.

In this example you can see how toJSON works correctly (with recursive shared types) on a newly created Doc, but doesn’t work until doc2.getMap is called when doc2 has been cloned with encodeStateAsUpdate.

import * as Y from 'yjs'

const doc1 = new Y.Doc()
const root = doc1.getMap('root')
const map1 = new Y.Map()
const map2 = new Y.Map()
root.set('map1', map1)
map1.set('map2', map2)
map2.set('foo', 'bar')

console.log('doc1', doc1.toJSON())
// { root: { map1: { map2: { foo: 'bar' } } } }

const doc2 = new Y.Doc()
Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1))
console.log('doc2', doc2.toJSON())
// { root: undefined }

doc2.getMap('root')
console.log('doc2 (after getMap)', doc2.toJSON())
// { root: { map1: { map2: { foo: 'bar' } } } }

Demo: View in CodeSandbox

2 Likes