Chris Muller uploaded a new version of JSON to project The Trunk: http://source.squeak.org/trunk/JSON-cmm.62.mcz
==================== Summary ====================
Name: JSON-cmm.62 Author: cmm Time: 24 February 2024, 1:21:44.121899 am UUID: 8a043f53-c158-4a2a-b29a-0fb20d53d6a9 Ancestors: JSON-cmm.61
- Prefix json extension methods to indicate they're intended for Json outputs. - Better comments, implementations and tests for new path accessing functions. - Fix and a test an Array of Associations to be a Json object.
=============== Diff against JSON-cmm.61 ===============
Item was removed: - ----- Method: Collection>>atPath: (in category '*json') ----- - atPath: anArray - "Assume I'm a set of nested HashedCollections and/or SequenceableCollections. Answer the object at the path of indices and/or keys identified in anArray." - ^ self - atPath: anArray - ifLost: [ : last | self error: 'path lost after' , last asString ]!
Item was removed: - ----- Method: Collection>>atPath:ifLost: (in category '*json') ----- - atPath: anArray ifLost: aBlock - "Assume I'm a set of nested HashedCollections and/or SequenceableCollections. Answer the object at the path of indices and/or keys identified in anArray. If the full path specified by anArray isn't present, cull aBlock with the last element present along the path." - | last | - ^ (self - path: anArray - do: [ : elem : node | last := node ]) - ifNil: [ aBlock cull: last ] - ifNotNil: [ last ]!
Item was added: + ----- Method: Collection>>jsonAtPath: (in category '*json') ----- + jsonAtPath: anArray + "Assume I'm Json output. Answer the object at the path of indices and/or keys identified in anArray. If the full path specified by anArray isn't found, signal an Error." + ^ self + jsonAtPath: anArray + ifLost: [ : last | self error: 'path lost after index ' , last asString ]!
Item was added: + ----- Method: Collection>>jsonAtPath:ifLost: (in category '*json') ----- + jsonAtPath: anArray ifLost: aBlock + "Assume I'm a Json object. Answer the sub-object at the path of indices and/or keys identified in anArray. If the full path specified by anArray isn't present, stop, and answer the value of aBlock culled with the index of the element within anArray that isn't found." + | last index | + index := 0. + ^ (self + jsonPath: anArray + do: + [ : elem : node | index := index + 1. + last := node ]) + ifNil: [ aBlock cull: index+1] + ifNotNil: [ last ]!
Item was added: + ----- Method: Collection>>jsonPath:do: (in category '*json') ----- + jsonPath: anArray do: aBlock + "Assume I'm a Json output. Value aBlock with each object along the path of keys and/or indices identified in anArray. If a path element isn't found, stop, and return nil, otherwise, return self." + | current | + current := self. + anArray do: + [ : eachKey | "Json doesn't have Characters, allow pathing into Json objects only." + (current isArray or: [ current isDictionary ]) + ifTrue: + [ current + at: eachKey + ifPresent: + [ : eachValue | aBlock + value: eachKey + value: eachValue. + current := eachValue ] + ifAbsent: [ ^ nil ] ] + ifFalse: [ ^ nil ] ]. + ^ self!
Item was added: + ----- Method: Collection>>jsonWriteListOn: (in category '*json') ----- + jsonWriteListOn: aStream + "By default, use array braces " + aStream nextPut: $[. + + self do: [:each | + each jsonWriteOn: aStream + ] separatedBy: [ aStream nextPut: $, ]. + + aStream nextPut: $]!
Item was added: + ----- Method: Collection>>jsonWriteObjectOn: (in category '*json') ----- + jsonWriteObjectOn: aStream + + | needComma | + needComma := false. + aStream nextPut: ${. + self associationsDo: [ :assoc | + needComma + ifTrue: [ aStream nextPut: $, ] + ifFalse: [ needComma := true ]. + assoc key jsonWriteOn: aStream. + aStream nextPut: $:. + assoc value jsonWriteOn: aStream ]. + aStream nextPut: $}.!
Item was changed: ----- Method: Collection>>jsonWriteOn: (in category '*json') ----- jsonWriteOn: aStream + (self notEmpty and: [self anyOne isVariableBinding]) + ifTrue: [ self jsonWriteObjectOn: aStream ] + ifFalse: [ self jsonWriteListOn: aStream ]! - "By default, use array braces " - aStream nextPut: $[. - - self do: [:each | - each jsonWriteOn: aStream - ] separatedBy: [ aStream nextPut: $, ]. - - aStream nextPut: $]!
Item was removed: - ----- Method: Collection>>path:do: (in category '*json') ----- - path: anArray do: aBlock - "Assume I'm a set of nested HashedCollections and/or SequenceableCollections. Value aBlock with each object along the path of indices and/or keys identified in anArray. If a path element isn't found, stop, and return nil, otherwise, return self." - anArray - inject: self - into: - [ : dictOrArray : pathElem | dictOrArray - at: pathElem - ifPresent: [ : node | aBlock value: pathElem value: node ] - ifAbsent: [ ^ nil ] ]. - ^ self!
Item was added: + ----- Method: JsonTests>>testNonDictionaryJsonObject (in category 'tests') ----- + testNonDictionaryJsonObject + self + render: {'a' -> 1. 'b' -> 2} + equals: '{"a":1,"b":2}'!
Item was added: + ----- Method: JsonTests>>testPathAccess (in category 'tests') ----- + testPathAccess + | json lost | json := Json readFrom: '{ + "smalltalk" : { + "platformName":"unix", + "classes" : [ + { "id" : 3557, "name":"Integer" }, + { "id" : 3558, "name":"Float" }, + { "id" : 3559, "name":"Fraction" } + ] + } + }' readStream. + self assert: (json jsonAtPath: #('smalltalk' 'platformName')) = 'unix'. + "With array in the path." + self assert: (json jsonAtPath: #('smalltalk' 'classes' 1 'id')) = 3557. + self assert: (json jsonAtPath: #('smalltalk' 'classes' 2 'name')) = 'Float'. + "No pathing into byte objects, only Json objects." + self + should: [json jsonAtPath: #('smalltalk' 'classes' 2 'name' "Not allowed --->" 1)] + raise: Error. + "Key not found." + self + should: [ json jsonAtPath: #('smalltalk' 'wordSize') ] + raise: Error. + lost := false. + json + jsonAtPath: #('smalltalk' 'wordSize') + ifLost: [ : lostPosition | lost := true. self assert: lostPosition = 2 ]. + self assert: lost. + "Index OOB" + self + should: [ json jsonAtPath: #('smalltalk' 'classes' 99) ] + raise: Error. + lost := false. + json + jsonAtPath: #('smalltalk' 'classes' 99) + ifLost: [ : lostPosition | lost := true. self assert: lostPosition = 3 ]. + self assert: lost!
Hi Chris,
On 2024. 02. 24. 7:21, commits@source.squeak.org wrote:
Chris Muller uploaded a new version of JSON to project The Trunk: http://source.squeak.org/trunk/JSON-cmm.62.mcz
==================== Summary ====================
Name: JSON-cmm.62 Author: cmm Time: 24 February 2024, 1:21:44.121899 am UUID: 8a043f53-c158-4a2a-b29a-0fb20d53d6a9 Ancestors: JSON-cmm.61
- Prefix json extension methods to indicate they're intended for Json outputs.
- Better comments, implementations and tests for new path accessing functions.
That's a bit better, though I still don't think a Bitset, Matrix or a Heap should understand any of those methods. I suggested something like the following to resolve that:
Json atPath: #('foo' 1) of: aJsonObject
instead of
aJsonObject atPath: #('foo' 1)
What do you think?
- Fix and a test an Array of Associations to be a Json object.
That is not a fix but a brand new feature. What is your use case for that?
Best, Levente
Hi Levente,
On 2024. 02. 24. 7:21, commits@source.squeak.org wrote:
Chris Muller uploaded a new version of JSON to project The Trunk: http://source.squeak.org/trunk/JSON-cmm.62.mcz
==================== Summary ====================
Name: JSON-cmm.62 Author: cmm Time: 24 February 2024, 1:21:44.121899 am UUID: 8a043f53-c158-4a2a-b29a-0fb20d53d6a9 Ancestors: JSON-cmm.61
- Prefix json extension methods to indicate they're intended for Json
outputs.
- Better comments, implementations and tests for new path accessing
functions.
That's a bit better, though I still don't think a Bitset, Matrix or a Heap should understand any of those methods.
Would you like #jsonPrintOn: removed for the same reason? There's also the matter of Object>>#asJsonString, and many many other examples of methods which are specific to some-but-not-all subclasses, being implemented in a common superclass which the applicable ones do inherit from.
I strongly prefer delegation of responsibility over utility methods, which are often not discovered because people aren't looking for them on various class-sides. I prefer instead to delete behaviors with #shouldNotImplement. I support if you wish to use that on methods on various subclasses to delete unwanted behaviors.
I suggested something like the following to resolve that:
Json atPath: #('foo' 1) of: aJsonObject
instead of
aJsonObject atPath: #('foo' 1)
What do you think?
- Fix and a test an Array of Associations to be a Json object.
That is not a fix but a brand new feature. What is your use case for that?
It's both. Because I'd Squeak to have the elegance of writing:
{'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } }
to create a Json object with code, instead of being required to remember and write out its internal Smalltalk representation (which has nothing to do with the Json spec), over and over again, as in:
{'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } as: OrderedDictionary } as: OrderedDictionary
Best, Chris
Hi Chris,
On 2024. 02. 27. 1:46, Chris Muller wrote:
Hi Levente,
On 2024. 02. 24. 7:21, commits@source.squeak.org <mailto:commits@source.squeak.org> wrote: > Chris Muller uploaded a new version of JSON to project The Trunk: > http://source.squeak.org/trunk/JSON-cmm.62.mcz <http://source.squeak.org/trunk/JSON-cmm.62.mcz> > > ==================== Summary ==================== > > Name: JSON-cmm.62 > Author: cmm > Time: 24 February 2024, 1:21:44.121899 am > UUID: 8a043f53-c158-4a2a-b29a-0fb20d53d6a9 > Ancestors: JSON-cmm.61 > > - Prefix json extension methods to indicate they're intended for Json outputs. > - Better comments, implementations and tests for new path accessing functions. That's a bit better, though I still don't think a Bitset, Matrix or a Heap should understand any of those methods.
Would you like #jsonPrintOn: removed for the same reason? There's also the matter of Object>>#asJsonString, and many many other examples of methods which are specific to some-but-not-all subclasses, being implemented in a common superclass which the applicable ones do inherit from.
Let's keep the discussion on the new features you just added, not existing features the community agreed on when adding the JSON package to Trunk.
I strongly prefer delegation of responsibility over utility methods, which are often not discovered because people aren't looking for them on various class-sides. I prefer instead to delete behaviors with #shouldNotImplement. I support if you wish to use that on methods on various subclasses to delete unwanted behaviors.
I suggested something like the following to resolve that: Json atPath: #('foo' 1) of: aJsonObject instead of aJsonObject atPath: #('foo' 1) What do you think? > - Fix and a test an Array of Associations to be a Json object. That is not a fix but a brand new feature. What is your use case for that?
It's both. Because I'd Squeak to have the elegance of writing:
It's just feels wrong to call a new feature a fix.
{'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } }
to create a Json object with code, instead of being required to remember and write out its internal Smalltalk representation (which has nothing to do with the Json spec), over and over again, as in:
{'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } as: OrderedDictionary } as: OrderedDictionary
JsonObject is already there to create json objects. Your example should be written as:
JsonObject new a: 1; b: (JsonObject new foo: 3; bar: 4).
There's no need to introduce a new DSL, especially an inferior one.
Best, Levente
What levente says.
On 27. Feb 2024, at 12:53, leves leves@caesar.elte.hu wrote:
Hi Chris,
On 2024. 02. 27. 1:46, Chris Muller wrote:
Hi Levente, On 2024. 02. 24. 7:21, commits@source.squeak.org mailto:commits@source.squeak.org wrote:
Chris Muller uploaded a new version of JSON to project The Trunk: http://source.squeak.org/trunk/JSON-cmm.62.mcz
http://source.squeak.org/trunk/JSON-cmm.62.mcz
==================== Summary ====================
Name: JSON-cmm.62 Author: cmm Time: 24 February 2024, 1:21:44.121899 am UUID: 8a043f53-c158-4a2a-b29a-0fb20d53d6a9 Ancestors: JSON-cmm.61
- Prefix json extension methods to indicate they're intended for
Json outputs.
- Better comments, implementations and tests for new path
accessing functions. That's a bit better, though I still don't think a Bitset, Matrix or a Heap should understand any of those methods. Would you like #jsonPrintOn: removed for the same reason? There's also the matter of Object>>#asJsonString, and many many other examples of methods which are specific to some-but-not-all subclasses, being implemented in a common superclass which the applicable ones do inherit from.
Let's keep the discussion on the new features you just added, not existing features the community agreed on when adding the JSON package to Trunk.
I strongly prefer delegation of responsibility over utility methods, which are often not discovered because people aren't looking for them on various class-sides. I prefer instead to delete behaviors with #shouldNotImplement. I support if you wish to use that on methods on various subclasses to delete unwanted behaviors. I suggested something like the following to resolve that: Json atPath: #('foo' 1) of: aJsonObject instead of aJsonObject atPath: #('foo' 1) What do you think?
- Fix and a test an Array of Associations to be a Json object.
That is not a fix but a brand new feature. What is your use case for that? It's both. Because I'd Squeak to have the elegance of writing:
It's just feels wrong to call a new feature a fix.
{'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } } to create a Json object with code, instead of being required to remember and write out its internal Smalltalk representation (which has nothing to do with the Json spec), over and over again, as in: {'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } as: OrderedDictionary } as: OrderedDictionary
JsonObject is already there to create json objects. Your example should be written as:
JsonObject new a: 1; b: (JsonObject new foo: 3; bar: 4).
There's no need to introduce a new DSL, especially an inferior one.
Best, Levente
HI Levente,
> Chris Muller uploaded a new version of JSON to project The Trunk:
> http://source.squeak.org/trunk/JSON-cmm.62.mcz <http://source.squeak.org/trunk/JSON-cmm.62.mcz> > > ==================== Summary ==================== > > Name: JSON-cmm.62 > Author: cmm > Time: 24 February 2024, 1:21:44.121899 am > UUID: 8a043f53-c158-4a2a-b29a-0fb20d53d6a9 > Ancestors: JSON-cmm.61 > > - Prefix json extension methods to indicate they're intended for Json outputs. > - Better comments, implementations and tests for new path accessing functions. That's a bit better, though I still don't think a Bitset, Matrix or a Heap should understand any of those methods.
Would you like #jsonPrintOn: removed for the same reason? There's also the matter of Object>>#asJsonString, and many many other examples of methods which are specific to some-but-not-all subclasses, being implemented in a common superclass which the applicable ones do inherit from.
Let's keep the discussion on the new features you just added,
I thought I was. First you said that Matrix shouldn't understand #atPath:, so I renamed it to #jsonAtPath: to match with #jsonWriteOn:.
Classes are a subclass of Behavior. Behavior's should be concerned with managing their behavior database (method dictionary) and creating instances, and that's about it. Making them do application behaviors like #jsonAtPath:, to use your polite phrase, "feels wrong". Application behavior is meant to be handled by the responsible object's instance methods, not the instance of its Class (a Behavior).
I offered a way we could delete the unwanted behaviors with #shouldNotImplement, but you ignored it. Smalltalk inheritance isn't perfect, but that's how things are done.
not existing features the community agreed on when adding the JSON package to Trunk.
I'm a member of the community or, at least currently, a member of the Core-Dev group. I've been using my own derivative of your Json package you graciously maintained in your library long before it went into trunk, keeping my enhancements to myself. Now that we've adopted Json into the trunk, I feel like it should belong to all of us, but this reaction tells me you don't want to set it free. It's understandable, but also feels like others' enhancements are not welcome if they happen to rub you the wrong way, regardless of merit.
I strongly prefer delegation of responsibility over utility methods, which are often not discovered because people aren't looking for them on various class-sides. I prefer instead to delete behaviors with #shouldNotImplement. I support if you wish to use that on methods on various subclasses to delete unwanted behaviors.
I suggested something like the following to resolve that: Json atPath: #('foo' 1) of: aJsonObject instead of aJsonObject atPath: #('foo' 1) What do you think? > - Fix and a test an Array of Associations to be a Json object. That is not a fix but a brand new feature. What is your use case for that?
It's both. Because I'd Squeak to have the elegance of writing:
It's just feels wrong to call a new feature a fix.
I felt it was a fix from the sense that the case exhibited by #testNonDictionaryJsonObject should never have blown up before.
{'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } }
to create a Json object with code, instead of being required to remember and write out its internal Smalltalk representation (which has nothing to do with the Json spec), over and over again, as in:
{'a' -> 1. 'b' -> { 'foo' -> 3. 'bar' -> 4 } as: OrderedDictionary } as: OrderedDictionary
JsonObject is already there to create json objects. Your example should be written as:
JsonObject new a: 1; b: (JsonObject new foo: 3; bar: 4).
There's no need to introduce a new DSL, especially an inferior one.
I respect your desire to lock the implementation into Dictionary and below. But please be fair, my example exactly matching Json syntax is unquestionably syntactically superior to yours for both reading and writing.
I was never a fan of JsonObject. The override of #doesNotUnderstand: means you want to send messages to it as if it's an object instead of just a piece of data. But because it inherits from Dictionary, it will never be an object. For the way you want to use JsonObject, it would've been better to inherit from Object and wrap its own dictionary, so that user classes could at least inherit from it where people can feel comfortable adding their own behaviors. As it is, it's a 3am production data-issue nightmare waiting to happen, as illustrated by this example:
self assert: (JsonObject new type: 'shirt'; size: 'M') size = 'M' "Assertion failed"
Maybe you believe you can "keep up with it" with more and more complicated overrides but, why? It's just accessors! The code of Json objects should be concerned with the domain of JSON, only! It should have API's for accessing attributes, paths, filters, etc. Maybe this philosophical difference about usage is where my path-accessing feature is giving you heartburn. I think it's dangerous to treat JsonObject's as domain objects. That's why I only ever treat it as a piece of data, and always override the default 'dictionaryClass' to Dictionary, and wrap it in my own domain object with the behaviors that access it with #at:, etc.
- Chris
On 2/28/24 05:43, Chris Muller wrote:
I thought I was. First you said that Matrix shouldn't understand #atPath:, so I renamed it to #jsonAtPath: to match with #jsonWriteOn:.
IMO Matrix, and for that matter any other object implementing #at:ifAbsent:, could and should absolutely understand #atPath:. It seems perfectly reasonable to me. In fact it seems like something that isn't even JSON-specific, it could be a core behaviour.
Perhaps we should rename #jsonAtPath: (and friends) back to #atPath: and put them on Object, which is where #at: (but not #at:ifAbsent:, unfortunately) is implemented.
your Json package
(Whose?)
I was never a fan of JsonObject.
Neither, FWIW. Plain old Dictionary was IMO sufficient.
self assert: (JsonObject new type: 'shirt'; size: 'M') size = 'M' "Assertion failed"
This is an excellent illustration of why JsonObject is a problem.
Perhaps we could consider removing JsonObject from the Json package? Perhaps it should live in its own non-trunk package, JsonObject?
Tony
squeak-dev@lists.squeakfoundation.org