Skip to content


Subversion checkout URL

You can clone with
Download ZIP



josephg opened this Issue · 8 comments

3 participants


We need a decent API for editing JSON objects.

I'm thinking of something like this:

Document paths are more easily written using strings, separated by dots (.). We'll have an escape character for literal dots. All functions which take path strings can take arrays instead.

Unfortunately, if we use \ as the escape character you'll have to write literal dots as \\.. I'm not sure what would be better.

Document API methods:

  • doc.get('a.b.c'): Get the object at path ['a','b','c']. Equivalent to doc.snapshot['a']['b']['c']

  • doc.set('a.b.c', 55): Set ['a','b'] to 55. This will emit an op which replaces whatever is there now with the new value. Cannot be used on strings or numbers.

  • doc.insert('a.b.4', 'hi'): This requires that there's a list, object or string at 'a.b'. If its a:

    • string: Insert 'hi' into the string at character position 4. (Throws exception if the inserted element isn't a string)
    • array: Insert 'hi' as a new element into the list
    • object: Set object[4] = 'hi'. Same as doc.set('a.b.4', 'hi').
  • doc.wrap('a.b.c'): Get a fake document object which wraps the element at ['a','b','c']. If the element at the named path is moved, the result should keep working. If this element is a:

    • string: The object has the API methods from text documents (getText(), insert(), del(), on('insert', fn), on('delete', fn)). Operations effect the named path. (This allows you to bind JSON document string elements to a text editor)
    • object or array: The object inherits the JSON API methods.
    • number: The object has add(val), subtract(val) and getValue() methods, as well as an .on('added', function(byValue) {...}) event handler.

We'll need some events as well... I'm not really sure what these should look like.

We could rename doc.get() to doc.getRaw() and call doc.wrap() doc.get() instead. I have a feeling that editing objects via wrappers will be the most common way to edit them.. but I'm not sure if this API is as clear.


Is doc.get('a','b','c') so bad?


... and doc.set('a', 'b', 'c', {})...

I'm not sure.. Maybe not. Maybe thats fine.

That syntax also allows doc.set(['a','b','c'], {}) which is good.


I remember writing down what I thought this should look like somewhere, but can't find it atm. I think it was something like:'a','b','c').set({})'a','b','c').get() // => {}
var player1 ='players',0)
var hp ='hp').get()'a',4).moveTo(0) // lm'a').push('asdf') // li at index 0'a').insert(0, 'asdf') // same as above'a').rename('b','c') // om'a','b').set('adsf') // od,oi (if there's something in 'a','b') or just oi otherwise'a').on('insert', ...) // listens to inserts at the exact path ['a', x]

It might be nice to maintain using the 'open' method even when getting these fake documents. I could imagine something like:'mycooldoc', 'json', function(doc, error) {
  // open a json document within a json document'a', 'json', function(doc, error){
      doc // <-- a sharejs json document-like object
      // text document with a json document within a json document'b', 'text', function(doc, error){
        doc // <-- a sharejs text document-like object

  // perhaps use something like nornagon's multiple argument syntax above
  // b is declared to be of type text explicitly, but a is of type json implicitly because
  // it contains b'a', 'b', 'text', function(doc, error){

@stevecrozz I like how the .open() syntax is that is consistant with how documents are opened. Like, its not clear that .wrap() or .at() gives another document-like object. Wordy though, and its important that the document wrapper is always created synchronously (otherwise the target could be moved between when you create the wrapper and when you get a handle to it).

To do that kind of thing, I've been playing with the idea of doing this:'mycooldoc/a/b', 'text', function(doc, error) {
  // mycooldoc is actually a JSON document, but doc is a handle to mycooldoc.a.b
  doc.insert('hi', 5);

... That'll need server support to be efficient. (Amongst other things we don't want all the irrelevant ops.) That would also be useful for pagination. I think I want to support stuff like that, but implement it later. ... and get something simpler / smaller / purely client side working now.

I like @nornagon's idea of just .at().set(), etc. Thats really minimal, elegant and readable. The thing is, the dot syntax is way nicer. ... And you're making paths a single argument, which is also nice.'').get()
// vs'options', 'graphics', 'fullscreen').get()

Unfortunately,"users.#{username}.password").set 'w3wt'

... which is fine for josephg but really really bad for joseph.g. I think its sufficiently bad to just go with your syntax, @nornagon.

One downside of @nornagon's syntax is that .get(), .set() and .at() share a namespace with the methods of subtypes (well, text). But maybe thats not so bad - maybe doc.get() and doc.get() can canonically mean 'get plain json' / 'replace contents with json' methods for text documents as well. (Text documents have a getText() method which I could rename).

So at the moment, I'm thinking of going with @nornagon's syntax.


Sounds good. I can't wait to take it for a spin.

@nornagon nornagon was assigned

Preliminary (mostly untested) version of this committed. See JSON Client API for details.

@nornagon nornagon closed this

Awesome :D

@aslakhellesoy aslakhellesoy referenced this issue from a commit
@aslakhellesoy aslakhellesoy Let me pass in a pg.Client so I can use the same connection from my a…
…pp instead of creating a 2nd connection. Similar to #30 and #31.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.