Getting the yType of any YObject

Good day!

I want to get the names and YTypes of all the shared types in my doc.
This is what I tried to do:

doc.share.forEach((value, key) => {
        var type = value._start.content.type.constructor.name
    });

This is a very hacky way, and it works well for YXmlElements, but not with YArrays.

Is there any consistent/proper method to get the YTypes for all the shared types in my doc?

Thanks,
Mansehej.

Hey @Mansehej

I think you already found a solution. Types are just a view on the shared data. You need to define beforehand how you want to interpret the top-level types (e.g. define “name” as Y.Text: getType(“name”, Y.Text)).

Is there any consistent/proper method to get the YTypes for all the shared types in my doc?

Not really. You are supposed to know beforehand which types exists.

Does this approach still work? While I can see the “share” property when logging a Y.Doc on a browser console, accessing that property always yields an empty Map (i.e., one with length 0 - however, if I log that Map as well, the browser still sees its entries - although the length is 0!)

That’s really strange…

[Edit] What I found out so far:

  • the loop shown above should probably be changed to
doc.share.forEach((value, key) => {
  let type = value.constructor.name
})
  • the strange output shown in my browser’s console was produced because doc.share was inspected before doc was completely synced from an IndexeddbPersistence. By waiting for the synced event, the output could be consolidated

Strange, when the entries of doc.share are loaded from persistence, they all seem to be created using the same constructor - independent of their original type.

But how does one then determine that original type?

[Edit] here is how I determine the type of a shared y.Doc entry (limited to y.Map, y.Array and y.Text)

  function TypeOfEntry (Entry) {
    if ((Entry._map instanceof Map) && (Entry._map.size > 0)) {
      return 'y.Map'
    }

    if ((Entry._start != null) && ('arr' in Entry._start.content)) {
      return 'y.Array'
    }

    if ((Entry._start != null) && ('str' in Entry._start.content)) {
      return 'y.Text'
    }

    return undefined
  }

  const sharedDoc = new Y.Doc()

  const Persistence = new IndexeddbPersistence('Yjs-Test-Content',sharedDoc)
  Persistence.on('synced', () => {
    sharedDoc.share.forEach((Value,Key) => {
      console.log(Key, TypeOfEntry(Value), Value)
    })
  })

It’s a horrible hack, I know…

ok, here is my current approach to detect the type of a shared type (or an element or entry of a shared type) regardless whether it is a top-level Y.Doc entry, a Y.Map entry or a Y.Array element

  function TypeOf (Entry) {
    switch (typeof Entry) {
      case 'undefined':
      case 'boolean':
      case 'number':
      case 'string': return typeof Entry
      case 'object':
        switch (true) {
          case (Entry == null): return 'null'

          case Array.isArray(Entry):          return 'tuple'
          case (Entry instanceof Uint8Array): return 'Uint8Array'

          case (Entry instanceof Y.Doc):         return 'Y.Doc'
          case (Entry instanceof Y.Map):         return 'Y.Map'
          case (Entry instanceof Y.Array):       return 'Y.Array'
          case (Entry instanceof Y.Text):        return 'Y.Text'
          case (Entry instanceof Y.XmlElement):  return 'Y.XmlElement'
          case (Entry instanceof Y.XmlFragment): return 'Y.XmlFragment'
          case (Entry instanceof Y.XmlText):     return 'Y.XmlText'

          case (Entry._map instanceof Map) && (Entry._map.size > 0):       return 'Y.Map'
          case (Entry._start != null) && ('arr' in Entry._start.content):  return 'Y.Array'
          case (Entry._start != null) && ('str' in Entry._start.content):  return 'Y.Text'
          case (Entry._start != null) && ('type' in Entry._start.content): return 'Y.XmlFragment'

          default: return 'struct'
        }
      default: return typeof Entry
    }
  }

tuples are plain JS arrays and structs plain JS objects

Right now, I cannot detect Y.XmlText and Y.XmlElement - they are reported as Y.Text or Y.XmlFragment, resp.

1 Like

Here is the current version of my Yjs data type detection method:

/**** tries to determine the type of a given Yjs data item ****/
// note: "Y.XmlElement"s will be identified as "Y.Map" and "XmlText"s as "Y.Text"

  function TypeOf (Entry) {
    switch (typeof Entry) {
      case 'undefined':
      case 'boolean':
      case 'number':
      case 'string': return typeof Entry
      case 'object':
        switch (true) {
          case (Entry == null): return 'null'

          case Array.isArray(Entry):          return 'tuple'
          case (Entry instanceof Uint8Array): return 'Uint8Array'

          case (Entry instanceof Y.Doc):         return 'Y.Doc'
          case (Entry instanceof Y.Map):         return 'Y.Map'
          case (Entry instanceof Y.Array):       return 'Y.Array'
          case (Entry instanceof Y.Text):        return 'Y.Text'
          case (Entry instanceof Y.XmlElement):  return 'Y.XmlElement'
          case (Entry instanceof Y.XmlFragment): return 'Y.XmlFragment'
          case (Entry instanceof Y.XmlText):     return 'Y.XmlText'

          case (Entry._map instanceof Map) && (Entry._map.size > 0):       return 'Y.Map'
          case (Entry._start != null) && ('arr' in Entry._start.content):  return 'Y.Array'
          case (Entry._start != null) && ('str' in Entry._start.content):
          case (Entry._start != null) && ('len' in Entry._start.content):  return 'Y.Text'
          case (Entry._start != null) && ('type' in Entry._start.content): return 'Y.XmlFragment'

          default: return 'struct'
        }
      default: return typeof Entry
    }
  }
2 Likes

Hi, @rozek, thank you for your code. I have just tested and it works almost perfect. Only one case failed:

it("should return Y.XmlText", () => {
    const item = new Y.XmlText();
    const itemType = yTypeOf(item);

    expect(itemType).toBe("Y.XmlText");
});
Expected: "Y.XmlText"
Received: "Y.Text"

…as indicated by the comment in the beginning.

This behaviour is due to limitations of Y.js - and I don’t know yet how to overcome them

This is because the Y.Text switch case comes before the Y.XmlText case. Y.XmlText inherits Y.Text, so instanceof Y.Text will incorrectly return true.

Moving the Y.XmlText case above the Y.Text case fixes it.

View in CodeSandbox

2 Likes

Thank you very much for this hint!

1 Like