NeoJSON extension - easier list handling with NeoJSONPropertyMapping

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

NeoJSON extension - easier list handling with NeoJSONPropertyMapping

Ben Coman
hi Sven,

With NeoJSON I have found several times in handling lists that
I need to indirect through  #for:customDo:  like this......

reader 
for: ExercismProblems 
do: [ :mapping |
self halt.
(mapper mapInstVar: #problems) valueSchema: #ArrayOfProblems ].
reader
for: #ArrayOfProblems
customDo: [  :mapping |
mapper listOfElementSchema: ExercismProblem ].


when I'd really like to be simpler and more concise like this...

reader 
for: ExercismProblems 
do: [ :mapping |
(mapping mapInstVar: #problems) listOfType: Array withElementSchema: ExercismProblem ].

so I'm seeking your feedback on the following hack which facilitates that.
(and if I missed something that already makes it simpler)

----------------------------
   Object subclass: #NeoJSONPropertyMapping
instanceVariableNames: 'propertyName valueSchema getter setter collectionClass' "Added collectionClass"


NeoJSONPropertyMapping >> listOfType: aCollectionClass withElementSchema: elementSchema
collectionClass := aCollectionClass.
valueSchema := elementSchema


   NeoJSONPropertyMapping >> readObject: anObject from: jsonReader
| value |
value := collectionClass 
ifNil: [jsonReader nextAs: valueSchema]  "nil by default retains the original behaviour"
ifNotNil: [jsonReader nextList: collectionClass of: valueSchema ].
setter value: anObject value: value

   
   NeoJSONReader >> nextList: nextListClass of: schema   "copied from #nextAs"
"Secondary interface to parse JSON.
Return a list of objects, each element according to schema."
^ nextListClass streamContents: [ :stream |
self parseListDo: [ 
stream nextPut: (self nextAs: schema) ] ]
----------------------------

with the following code to access it.

Object subclass: #ExercismAPI
instanceVariableNames: 'client rawResponse jsonResponse success message result'
classVariableNames: 'ApiKey'
package: 'Exercism'


ExercismAPI  class >> configureApiKey: key
ApiKey := key. 


ExercismAPI >> track: trackIdString
self path: 'v2/exercises/' , trackIdString.


ExercismAPI >> fetchTrack: trackIdString
|znclient response|
ApiKey ifNil: [ self error: 'ApiKey not configured. See class-side ExcercismAPI' ].
znclient := ZnClient new
https;
enforceHttpSuccess: true;
accept: ZnMimeType applicationJson;
host: 'x.exercism.io' .
znclient path: 'v2/exercises/' , trackIdString.
znclient queryAt: 'key' put: ApiKey.
^ [ response := znclient get ] 
on: ZnHttpUnsuccessful
do: [ :exception | Transcript crShow: exception ]


fetchJsonTrack: trackIdString
| reader |
reader := NeoJSONReader on: (self fetchTrack: trackIdString) readStream.
reader 
for: ExercismProblems 
do: [ :mapper |
(mapper mapInstVar: #problems) listOfType: Array andElementSchema: ExercismProblem ].  
^ reader nextAs: ExercismProblems 


In Playground...

ExercismAPI configureApiKey: 'ec60c50cb50d4ffaa97903d5ebb3d5ff'.

(ExercismAPI new fetchJsonTrack: 'go') inspect


I'll reset the apikey shortly.  You can get a new one from http://exercism.io/account/key.

cheers -ben
Reply | Threaded
Open this post in threaded view
|

Re: NeoJSON extension - easier list handling with NeoJSONPropertyMapping

Sven Van Caekenberghe-2
Hmm, Ben,

Did you see NeoJSONCustomMapping's mapping method category ?

There you can already find #listOfElementSchema: #listOfType: #listOfType:andElementSchema: as well as #mapWithValueSchema:

I am guessing they do what you want, no ? Or am I missing something as well ?

Sven

> On 16 Jun 2018, at 20:27, Ben Coman <[hidden email]> wrote:
>
> hi Sven,
>
> With NeoJSON I have found several times in handling lists that
> I need to indirect through  #for:customDo:  like this......
>
> reader
> for: ExercismProblems
> do: [ :mapping |
> self halt.
> (mapper mapInstVar: #problems) valueSchema: #ArrayOfProblems ].
> reader
> for: #ArrayOfProblems
> customDo: [  :mapping |
> mapper listOfElementSchema: ExercismProblem ].
>
>
> when I'd really like to be simpler and more concise like this...
>
> reader
> for: ExercismProblems
> do: [ :mapping |
> (mapping mapInstVar: #problems) listOfType: Array withElementSchema: ExercismProblem ].
>
> so I'm seeking your feedback on the following hack which facilitates that.
> (and if I missed something that already makes it simpler)
>
> ----------------------------
>    Object subclass: #NeoJSONPropertyMapping
> instanceVariableNames: 'propertyName valueSchema getter setter collectionClass' "Added collectionClass"
>
>
> NeoJSONPropertyMapping >> listOfType: aCollectionClass withElementSchema: elementSchema
> collectionClass := aCollectionClass.
> valueSchema := elementSchema
>
>
>    NeoJSONPropertyMapping >> readObject: anObject from: jsonReader
> | value |
> value := collectionClass
> ifNil: [jsonReader nextAs: valueSchema]  "nil by default retains the original behaviour"
> ifNotNil: [jsonReader nextList: collectionClass of: valueSchema ].
> setter value: anObject value: value
>
>    
>    NeoJSONReader >> nextList: nextListClass of: schema   "copied from #nextAs"
> "Secondary interface to parse JSON.
> Return a list of objects, each element according to schema."
>
> ^ nextListClass streamContents: [ :stream |
> self parseListDo: [
> stream nextPut: (self nextAs: schema) ] ]
> ----------------------------
>
> Sample data is shown at: https://github.com/bencoman/pharogui-exercism/blob/master/README.md
> with the following code to access it.
>
> Object subclass: #ExercismAPI
> instanceVariableNames: 'client rawResponse jsonResponse success message result'
> classVariableNames: 'ApiKey'
> package: 'Exercism'
>
>
> ExercismAPI  class >> configureApiKey: key
> ApiKey := key.
>
>
> ExercismAPI >> track: trackIdString
> self path: 'v2/exercises/' , trackIdString.
>
>
> ExercismAPI >> fetchTrack: trackIdString
> |znclient response|
> ApiKey ifNil: [ self error: 'ApiKey not configured. See class-side ExcercismAPI' ].
> znclient := ZnClient new
> https;
> enforceHttpSuccess: true;
> accept: ZnMimeType applicationJson;
> host: 'x.exercism.io' .
> znclient path: 'v2/exercises/' , trackIdString.
> znclient queryAt: 'key' put: ApiKey.
> ^ [ response := znclient get ]
> on: ZnHttpUnsuccessful
> do: [ :exception | Transcript crShow: exception ]
>
>
> fetchJsonTrack: trackIdString
> | reader |
> reader := NeoJSONReader on: (self fetchTrack: trackIdString) readStream.
> reader
> for: ExercismProblems
> do: [ :mapper |
> (mapper mapInstVar: #problems) listOfType: Array andElementSchema: ExercismProblem ].
> ^ reader nextAs: ExercismProblems
>
>
> In Playground...
>
> ExercismAPI configureApiKey: 'ec60c50cb50d4ffaa97903d5ebb3d5ff'.
>
> (ExercismAPI new fetchJsonTrack: 'go') inspect
>
>
> I'll reset the apikey shortly.  You can get a new one from http://exercism.io/account/key.
>
> cheers -ben


Reply | Threaded
Open this post in threaded view
|

Re: NeoJSON extension - easier list handling with NeoJSONPropertyMapping

Ben Coman


On 17 June 2018 at 02:47, Sven Van Caekenberghe <[hidden email]> wrote:
Hmm, Ben,

Did you see NeoJSONCustomMapping's mapping method category ? 

There you can already find #listOfElementSchema:   #listOfType: 
#listOfType:andElementSchema: as well as #mapWithValueSchema:

Yes. Indeed thats where I copied my selector naming from, although I switched "with" for ''and" 
NeoJSONPropertyMapping >> listOfType:withElementSchema:  

However NeoJSONCustomMappings seems only accessible through "reader for: ... customDo: "
while instance variable interaction seems only possible using "reader for: ... do:" 
So it seems NeoJSONCustomMapping lacks #mapAccessor* methods
while NeoJSONObjectMapping lacks #listOfType:* methods
and "never the twain shall meet".

I didn't look closely at #mapWithValueSchema:
but its covering tests seem to operate on Dictionaries rather than Arrays.

 
I am guessing they do what you want, no ? Or am I missing something as well ?

Yes, but how to mix those list* methods with poking the result into an instance variable
to be able to "assign a list of elements to an instance variable" as a one liner?
I'd be glad to be shown another way to do this for the given data.

I see a few #testLists examples, but none that assign the result to an instance variable.
NeoJSONExamplesTests has some "lists of objects" tests, but no "objects with lists"
A useful example would be #testPolygonPoints... 

NeoJSONExamplesTests >> testPolygonPoints
| polygon polygonJson result |
polygon := Polygon new vertices: { 1@2. 3@4. 5@6 }.
polygonJson := 
'{ "vertices": [    "I'm not clear on how to do the writer side of things"
{
"x" : 1,
"y" : 2
},
{
"x" : 3,
"y" : 4
},
{
"x" : 5,
"y" : 6
}]
}'.
result := (NeoJSONReader on: polygonJson readStream)
mapInstVarsFor: Point;
for: Polygon do: [ :mapping | 
(mapping mapInstVar: #vertices) listOfType: Array withElementSchema: Point];
nextAs: Polygon.
self assert: result equals: polygon


Some equality support is needed for Polygon (PathShape)
(my best guess, discussion of this might be worth a separate thread)

PathShape >> = aPathShape
|otherVertices equal|
otherVertices := aPathShape vertices.
(self species = aPathShape species) & (vertices size = otherVertices size) 
ifFalse: [ ^false ].
1 to: vertices size do: [ :i | 
(vertices at: i) = (otherVertices at: i) ifFalse: [^false] ].
^true

PathShape >> hash
|hash|
hash := 0.
1 to: vertices size do: [ :i | hash bitXor: (vertices at: i) ].
^hash  

cheers -ben
 

Sven

> On 16 Jun 2018, at 20:27, Ben Coman <[hidden email]> wrote:
>
> hi Sven,
>
> With NeoJSON I have found several times in handling lists that
> I need to indirect through  #for:customDo:  like this......
>
>       reader
>               for: ExercismProblems
>               do: [ :mapping |
>                       self halt.
>                       (mapper mapInstVar: #problems) valueSchema: #ArrayOfProblems ].
>       reader
>               for: #ArrayOfProblems
>               customDo: [  :mapping |
>                       mapper listOfElementSchema: ExercismProblem ].
>
>
> when I'd really like to be simpler and more concise like this...
>
>       reader
>               for: ExercismProblems
>               do: [ :mapping |
>                       (mapping mapInstVar: #problems) listOfType: Array withElementSchema: ExercismProblem ].
>
> so I'm seeking your feedback on the following hack which facilitates that.
> (and if I missed something that already makes it simpler)
>

cheers -ben