Embedding object properties in NeoJSON mappings

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Embedding object properties in NeoJSON mappings

Esteban A. Maringolo
Hi all (again),

As part of writing the port (and adaptation to use Zinc and NeoJSON),
a CouchDB JSON-REST API, now I find something I don't know how to
solve using NeoJSON mappings.

All objects you store in CouchDB at a particular URI are saved as
"Documents", so if you save a dictionary with keys "name", "height",
"c" to  "/people/1234" , when you query "people/1234" you get the keys
of the saved dictionary PLUS to extra keys called _id and _rev.

Then when you query (GET) back the document you get a JSON like this:
{"_id": ..., "_rev": ..., "name": ..., "height":...,}

My Idea is that I can wrap any object serializable as JSON within
aDocument, but then if I define CouchDocument class>>#neoJsonMapping:
mapper as:

 mapper for: self do: [ :mapping |
  mapping mapAccessor: #id to: '_id'.
  mapping mapAccessor: #revision to: '_rev'.
  mapping mapAccessor: #class to: '_class'
 ].

I have no way to "flatten"/"merge" anObject within aDocument.

I would expect something like

  mapper for: self do: [ :mapping |
    mapping mapAccessor: #id to: '_id'.
    mapping mapAccessor: #revision to: '_rev'.
    mapping mapAccessor: #class to: '_class'
    mapping embed: [:object | object whatToEmbed]
 ].

Of course this is awkward, because if I need to read it back, the
"extraction"  of the embeded properties needs to know which belongs to
which and more importantly , what to instantiate!

Of course I can do it manually without using NeoJSON (It's already
being done [1]) , but I like when different libraries things fit
naturally together :)


Am I stretching it too much? :)

Regards,


[1] Currently the writing is done manually and the refresh (which is
also a GET) does the following:

CouchDocument>>#refresh
" Update this Document to the latest revision "
| response |
response := self information. "response is a Dictionary"
revision := response at: '_rev'.
object := response couchToObject.
^response

And here happens the magic, it searchs for a "_class" key which if
present will use to instantiate the object "wrapped" by the document.

Dictionary>>couchToObject
| class object |
self removeKey: '_id' ifAbsent: [].
self removeKey: '_rev' ifAbsent: [].
self keysAndValuesDo: [:key :value | self at: key put: value couchToObject].
class := (self at: '_class' ifAbsent: [^self]) asSymbol asClass.
object := class basicNew.
object jsonInstanceVariableNames keysAndValuesDo: [:index :key |
   self at: key ifPresent: [:value | object instVarAt: index put: value]].
^object

#jsonInstanceVariableNames is another level of "mapping" here, that
defined what to map from the Document response.


Esteban A. Maringolo

Reply | Threaded
Open this post in threaded view
|

Re: Embedding object properties in NeoJSON mappings

Sven Van Caekenberghe-2
Hi,

> On 22 Feb 2019, at 02:05, Esteban Maringolo <[hidden email]> wrote:
>
> Hi all (again),
>
> As part of writing the port (and adaptation to use Zinc and NeoJSON),
> a CouchDB JSON-REST API, now I find something I don't know how to
> solve using NeoJSON mappings.
>
> All objects you store in CouchDB at a particular URI are saved as
> "Documents", so if you save a dictionary with keys "name", "height",
> "c" to  "/people/1234" , when you query "people/1234" you get the keys
> of the saved dictionary PLUS to extra keys called _id and _rev.
>
> Then when you query (GET) back the document you get a JSON like this:
> {"_id": ..., "_rev": ..., "name": ..., "height":...,}
>
> My Idea is that I can wrap any object serializable as JSON within
> aDocument, but then if I define CouchDocument class>>#neoJsonMapping:
> mapper as:
>
> mapper for: self do: [ :mapping |
>  mapping mapAccessor: #id to: '_id'.
>  mapping mapAccessor: #revision to: '_rev'.
>  mapping mapAccessor: #class to: '_class'
> ].
>
> I have no way to "flatten"/"merge" anObject within aDocument.
>
> I would expect something like
>
>  mapper for: self do: [ :mapping |
>    mapping mapAccessor: #id to: '_id'.
>    mapping mapAccessor: #revision to: '_rev'.
>    mapping mapAccessor: #class to: '_class'
>    mapping embed: [:object | object whatToEmbed]
> ].
>
> Of course this is awkward, because if I need to read it back, the
> "extraction"  of the embeded properties needs to know which belongs to
> which and more importantly , what to instantiate!
>
> Of course I can do it manually without using NeoJSON (It's already
> being done [1]) , but I like when different libraries things fit
> naturally together :)
>
>
> Am I stretching it too much? :)

Yes, this is out of the scope of NeoJSON.

As you know, JSON is strictly limited and kept simple, by design. Things people do to shoehorn objects, classes, types, ids, collections, references, etc on to it are exactly that, hacks on top. There are many ways to try to do any of this.

What you are doing now, a two pass approach, is what I would do too: first use NeoJSON to get a standard structure (maybe use NeoJSONObject for simplicity), next do some extra work - and vice versa.

Note that Pharo itself might lack some meta info to describe a mapping by itself (collections have no element types for example), so a good mapping is always necessary to go to a statically typed model.

There is a reason STON exists, it is one answer to this problem.

Sven

> Regards,
>
>
> [1] Currently the writing is done manually and the refresh (which is
> also a GET) does the following:
>
> CouchDocument>>#refresh
> " Update this Document to the latest revision "
> | response |
> response := self information. "response is a Dictionary"
> revision := response at: '_rev'.
> object := response couchToObject.
> ^response
>
> And here happens the magic, it searchs for a "_class" key which if
> present will use to instantiate the object "wrapped" by the document.
>
> Dictionary>>couchToObject
> | class object |
> self removeKey: '_id' ifAbsent: [].
> self removeKey: '_rev' ifAbsent: [].
> self keysAndValuesDo: [:key :value | self at: key put: value couchToObject].
> class := (self at: '_class' ifAbsent: [^self]) asSymbol asClass.
> object := class basicNew.
> object jsonInstanceVariableNames keysAndValuesDo: [:index :key |
>   self at: key ifPresent: [:value | object instVarAt: index put: value]].
> ^object
>
> #jsonInstanceVariableNames is another level of "mapping" here, that
> defined what to map from the Document response.
>
>
> Esteban A. Maringolo
>


Reply | Threaded
Open this post in threaded view
|

Re: Embedding object properties in NeoJSON mappings

Esteban A. Maringolo
Hi,

El vie., 22 feb. 2019 a las 7:55, Sven Van Caekenberghe
(<[hidden email]>) escribió:
> > On 22 Feb 2019, at 02:05, Esteban Maringolo <[hidden email]> wrote:
> > Am I stretching it too much? :)
>
> Yes, this is out of the scope of NeoJSON.
>
> As you know, JSON is strictly limited and kept simple, by design.
> Things people do to shoehorn objects, classes, types, ids, collections, references, etc on to it are exactly that, hacks on top.
> There are many ways to try to do any of this.

I though so. :)

> What you are doing now, a two pass approach, is what I would do too: first use NeoJSON
> to get a standard structure (maybe use NeoJSONObject for simplicity),
> next do some extra work - and vice versa.

> Note that Pharo itself might lack some meta info to describe a mapping by itself
> (collections have no element types for example), so a good mapping is always
> necessary to go to a statically typed model.
>
> There is a reason STON exists, it is one answer to this problem.

Thanks, I'll look into it to see if I can adapt it to my needs.

Regards,