> On 25 Sep 2017, at 17:14, Juraj Kubelka <juraj.kube...@icloud.com> wrote: > > Hi Sven, > > thank you! > > In the NeoJSON repository, you likely want to merge > Neo-JSON-Core-SvenVanCaekenberghe.43 and 44. And the same for the test cases.
I did, thanks ! >> On Sep 24, 2017, at 06:05, Sven Van Caekenberghe <s...@stfx.eu> wrote: >> >> Hi Juraj, >> >> This would be a simpler form of the type/class tags that are often used in >> JSON encoding. Since there are many ways to do this, there cannot be one >> solution. NeoJSON mapping was not designed to cover these cases. Nor is JSON >> meant to do this. STON is one (rather elegant if I may say so) answer to >> this problem. Your example would be encoded as >> >> [ >> PngAttachement { #url:'http://example.com/random-name.txt', >> #fileName:'chapter-one.txt' }, >> TxtAttachement { #url:'http://example.com/random-name.png', >> #fileName:'image.png' } >> ] > > About STON usage, I wonder how it works in terms of a data structure > evolution. Currently I use JSON format to store a data permanently. The data > are supposed to be accesible for a long time (years). I am pretty sure that > the protocol will evolve and we will end up with several JSON versions. I > think that having different JSON mapping schemes will allow to map old JSON > versions to a latest object structure (or to several different versions if > necessary). > > For the example above, think of the scenario that PngAttachment does not > exist anymore. In a simple scenario there could be an ImageAttachment class > instead with the same instance variables. In a more complex scenario, there > could be a AttachmentTwo { #type: AttachmentTwoType { … instances … }, #path: > AttachmentTwoPath { … instances … } }. > > I can use JSON mappings that transform the old JSON version to the new class > structure. I am not sure if STON is that flexible. > What do you think? Do I miss something? STON has no options for more than one mapping (the 'system' one). Schema evolution is always a hard problem, using temporary structures seems the simplest solution. The STON specification has not changed significantly since I first wrote it years ago. The implementation and the number of cases handled has though. >> Still, the question of how to do this kind of dynamic decoding is an >> interesting challenge. Please update to the following commits: >> >> === >> Name: Neo-JSON-Core-SvenVanCaekenberghe.44 >> Author: SvenVanCaekenberghe >> Time: 22 September 2017, 3:24:51.679449 pm >> UUID: f1ebeade-2816-0d00-a04c-ae370e598362 >> Ancestors: Neo-JSON-Core-SvenVanCaekenberghe.42 >> >> Implement some missing code in NeoJSONCustomMapping (the writing part of >> #listOfElementSchema: #listOfType:andElementSchema: #mapWithValueSchema:) >> >> Add NeoJSONStreamingWriter>>#writeElementAs: >> >> Add NeoJSONMappingTests>>#testDynamicTyping as an example >> === >> Name: Neo-JSON-Tests-SvenVanCaekenberghe.41 >> Author: SvenVanCaekenberghe >> Time: 22 September 2017, 3:25:09.881494 pm >> UUID: a9a900e0-2816-0d00-a04d-a6fb0e598362 >> Ancestors: Neo-JSON-Tests-SvenVanCaekenberghe.39 >> >> Implement some missing code in NeoJSONCustomMapping (the writing part of >> #listOfElementSchema: #listOfType:andElementSchema: #mapWithValueSchema:) >> >> Add NeoJSONStreamingWriter>>#writeElementAs: >> >> Add NeoJSONMappingTests>>#testDynamicTyping as an example >> === >> >> Now you can do as follows: >> >> NeoJSONMappingTests>>#testDynamicTyping >> | data customMapping json result | >> data := Array with: #foo->1 with: #(foo 2). >> "The idea is to map a key value combination as either a classic association >> or a simple pair, >> using key & value properties as well as a type property to distinguish >> between the two" >> customMapping := [ :mapper | >> mapper >> for: #AssocOrPair customDo: [ :mapping | >> mapping >> encoder: [ :x | >> x isArray >> ifTrue: [ { #type->#pair. #key->x first. #value->x second } >> asDictionary ] >> ifFalse: [ { #type->#assoc. #key->x key. #value->x value } >> asDictionary ] ]; >> decoder: [ :x | >> (x at: #type) = #pair >> ifTrue: [ Array with: (x at: #key) with: (x at: #value) ] >> ifFalse: [ (x at: #key) -> (x at: #value)] ] ]; >> for: #ArrayOfAssocOrPair customDo: [ :mapping | >> mapping listOfType: Array andElementSchema: #AssocOrPair ]; >> yourself ]. >> json := String streamContents: [ :out | >> (customMapping value: (NeoJSONWriter on: out)) nextPut: data as: >> #ArrayOfAssocOrPair ]. >> result := (customMapping value: (NeoJSONReader on: json readStream)) nextAs: >> #ArrayOfAssocOrPair. >> self assert: result equals: data >> >> Everything is virtual (not implemented as methods on actual classes) which >> makes the example self contained and non intrusive, but not very elegant, >> nor object oriented or extendable. Also, it is not as efficient as NeoJSON >> mapping was designed for, since it creates intermediate structures. > > Nice example. If #decoder: and #encoder: are used, is it still possible to > use inner mappings? For example in my previous example, one instance variable > is a ZnUrl instance that is stored as a String. Can I say “use ZnUrl mapping > in for this intermediate structure”? Well, I understand that I can hard code > it directly in the the #decoder: and #encoder: messages using #asZnUrl and > #asString messages. I am thinking about a more complicated structures that > involves more mapping definitions. Yes, other mappings should be respected. Sven > Thanks! > Juraj > >> >> HTH, >> >> Sven >> >>> On 21 Sep 2017, at 22:07, Juraj Kubelka <juraj.kube...@icloud.com> wrote: >>> >>> Hi, >>> >>> By studying the NeoJSON book chapter (Pharo Enterprise), I do not >>> understand how to modify the following example: >>> >>> -=-=-=-=-=-=-=- >>> "Let us say that we have an Attachment class..." >>> >>> Object subclass: #Attachment >>> instanceVariableNames: 'url fileName' >>> classVariableNames: '' >>> package: 'NeoJSON-Use-Case'. >>> >>> "...with url: and fileName: methods." >>> Attachment compile: 'url: anObject', String cr, String tab, 'url := >>> anObject' classified: 'accessing'. >>> Attachment compile: 'fileName: anObject', String cr, String tab, 'fileName >>> := anObject' classified: 'accessing'. >>> >>> "Let's create a collection of two instances:" >>> collectionOne := { >>> Attachment new >>> url: 'http://example.com/random-name.txt' asZnUrl; >>> fileName: 'chapter-one.txt' >>> yourself. >>> Attachment new >>> url: 'http://example.com/random-name.png' asZnUrl; >>> fileName: 'image.png'; >>> yourself. >>> }. >>> >>> "And let's map it to a JSON structure:" >>> String streamContents: [ :aStream | >>> (NeoJSONWriter on: aStream) >>> for: #CollectionOfAttachments customDo: [ :mapping | >>> mapping listOfElementSchema: Attachment ]; >>> mapAllInstVarsFor: Attachment; >>> for: ZnUrl customDo: [ :mapping | >>> mapping encoder: [ :aZnUrl | >>> aZnUrl asString ] ]; >>> nextPut: collectionOne as: #CollectionOfAttachments. >>> ]. >>> >>> "And read the JSON structure:" >>> (NeoJSONReader on: >>> '[{"url":"http://example.com/random-name.txt","fileName":"chapter-one.txt"},{"url":"http://example.com/random-name.png","fileName":"image.png"}]' >>> readStream) >>> for: #CollectionOfAttachments customDo: [ :mapping | >>> mapping listOfElementSchema: Attachment ]; >>> for: Attachment do: [ :mapping | >>> mapping mapInstVar: 'fileName'. >>> (mapping mapInstVar: 'url') valueSchema: ZnUrl ]; >>> for: ZnUrl customDo: [ :mapping | >>> mapping decoder: [ :string | >>> string asZnUrl ] ]; >>> nextAs: #CollectionOfAttachments. >>> >>> "=======================" >>> “The previous example works perfectly, including the ZnUrl mapping (notice >>> that url variables have ZnUrl instances). >>> >>> Now, let's say that we want to distinguish PNG and TXT attachments. >>> For that reason we will create two Attachment subclasses..." >>> >>> Attachment subclass: #PngAttachment >>> instanceVariableNames: '' >>> classVariableNames: '' >>> package: 'NeoJSON-Use-Case'. >>> >>> Attachment subclass: #TxtAttachment >>> instanceVariableNames: '' >>> classVariableNames: '' >>> package: 'NeoJSON-Use-Case'. >>> >>> "...with type methods that might be used in a new JSON structure:" >>> PngAttachment compile: 'type', String cr, String tab, '^ ''png''' >>> classified: 'accessing'. >>> TxtAttachment compile: 'type', String cr, String tab, '^ ''txt''' >>> classified: 'accessing'. >>> >>> "Let's create a collection with the PNG and TXT instances:" >>> collectionTwo := { >>> TxtAttachment new >>> url: 'http://example.com/random-name.txt' asZnUrl; >>> fileName: 'chapter-one.txt' >>> yourself. >>> PngAttachment new >>> url: 'http://example.com/random-name.png' asZnUrl; >>> fileName: 'image.png'; >>> yourself. >>> }. >>> >>> "How can I modify NeoJSONWriter and NeoJSONReader mappings? >>> >>> The JSON structure should (ideally) looks like this:" >>> >>> '[ >>> >>> {“type”:”txt”,"url":"http://example.com/random-name.txt","fileName":"chapter-one.txt”}, >>> >>> {“type”:”png”,"url":"http://example.com/random-name.png","fileName":"image.png”} >>> ]' >>> -=-=-=-=-=-=-=- >>> >>> I would like to keep the mapping isolated (without defining neoJsonOn: >>> methods). >>> >>> Thank you! >>> Juraj