JSON Dumper/Parser

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

JSON Dumper/Parser

Robin Redeker-2
Hi!

I've written a JSON dumper and parser some time ago
and haven't got around finishing it up for more smooth
usage. As I'm currenlty quite busy with my studies
I probably wont get around fixing the remaining issues:

   -  Unicode handling is not implemented and not really taken
      care of, this means you can probably only send ascii JSON
      around.
      Unicode needs to be implemented and somehow taken care of
      before this implementation can be called a real JSON dumper/parser.

   -  Also the handling of the '\uXXXX' escaping in JSON needs some
      thought and implementation (I'm not very familiar with the code anymore
      atm.)

   -  Maybe also the datatypes for json objects and arrays could be made configurable
      somehow. It's currently limited to generating Dictionary and OrderedCollection
      from a JSON string.
      The dumping works for Dictionaries and SequenceableCollection.

   -  The dumping of floats is currently just done by: (self asFloat) printString
      I don't know whether that always gives a JSON compatible representation.
      I only remember that I was too lazy to write a float dumper back then.
      Someone should take a look at this or at least keep it in mind.

So, before the code gets lost I've attached it to this mail (json.st).

There are some comments in the code and I hope it's mostly understandable.

When I have more time for coding I'll probably fix the issues, but I don't see
that in the near future (~3-4 months).

If anyone is interested feel free to fix the issues (just drop a mail to the ML
if you have done so, so that I don't fix already fixed things :).

Here is a short example:

   ~/devel/smalltalk# gst
   GNU Smalltalk ready

   st> FileStream fileIn: 'json.st'!
   FileStream
   st> JSONDumper toJSON: 'fooo'!
   '"fooo"'
   st> JSONDumper fromJSON: '{"a":"b"}'!
   Dictionary new: 32 "<0x2ad8f4e93be0>"
   st> (JSONDumper fromJSON: '{"a":"b"}') at: 'a'!
   'b'


Greetings,
   Robin

_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk

json.st (8K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: JSON Dumper/Parser

Paolo Bonzini-2

>    -  Maybe also the datatypes for json objects and arrays could be made configurable
>       somehow. It's currently limited to generating Dictionary and OrderedCollection
>       from a JSON string.
>       The dumping works for Dictionaries and SequenceableCollection.
>
> So, before the code gets lost I've attached it to this mail (json.st).
>
> There are some comments in the code and I hope it's mostly understandable.

I've fixed most issues except the one above.

In addition, I changed your code so that: 1) for reading, the lexer
*uses* a stream instead of subclassing it; the stream is in an instance
variable, so that the reading happens in the instance side of the
JSONReader class; 2) the writing is done to a stream, which eliminates
the need to construct temporary streams in toJSON.

There was one bug: the code was subject to infinite loops in the
presence of invalid JSON input such as '2.5d'.

While I don't have time to make it into a real gst package, it will be
done in due time (especially if somebody gets an idea on how to
implement generating other classes than dictionaries).

Paolo


Stream subclass: #JSONReader
    instanceVariableNames: 'stream'
    classVariableNames: ''
    poolDictionaries: ''
    category: nil !

JSONReader comment:
'I read and write data structures (currently build of OrderedCollection and Dictionary)
from and to JSON (Java Script Object Notation). Note: I will behave badly with circular
data structures.' !

JSONReader comment:
'I''m a helper class for JSONReader. I know what is considered whitespace
for JSON and have some helper methods JSONReader uses.' !

!JSONReader class methodsFor: 'json'!

on: aStream
    ^self new stream: aStream
!

!JSONReader methodsFor: 'json'!

stream: aStream
    stream := aStream
!

peek
   "I'm peeking for the next non-whitespace character and will drop all whitespace in front of it"
   | c |
   [
     c := stream peek.
     c = (Character space)
         or: [ c = (Character tab)
         or: [ c = (Character lf)
         or: [ c = (Character cr)]]]
   ] whileTrue: [
     stream next
   ].
   ^c
!

next
   "I'm returning the next non-whitespace character"
   | c |
   c := self peek.
   c isNil ifTrue: [ ^self error: 'expected character but found end of stream' ].
   stream next.
   ^c
! !

!JSONReader class methodsFor: 'json'!

toJSON: anObject
   "I'm returning a JSON string which represents the object."
   ^anObject toJSON
!

fromJSON: string
   "I'm responsible for decoding the JSON string to objects."
   ^(self on: string readStream) nextJSONObject
! !

!JSONReader methodsFor: 'private'!

nextJSONObject
   "I decode a json self to a value, which will be one of: nil,
true, false, OrderedCollection, Dictionary, String or Number
(i will return Integer or Float depending on the input)."
   | c |
   c := self peek.
   (c = $n) ifTrue: [ self next: 4. ^nil   ].
   (c = $t) ifTrue: [ self next: 4. ^true  ].
   (c = $f) ifTrue: [ self next: 5. ^false ].
   (c = ${) ifTrue: [ ^self nextJSONDict ].
   (c = $[) ifTrue: [ ^self nextJSONArray  ].
   (c = $") ifTrue: [ ^self nextJSONString ].
   ^self nextJSONNumber
!

nextJSONArray
   "I decode JSON arrays from the self and will return a OrderedCollection for them."
   | c obj value |
   obj := OrderedCollection new.
   self next.
   [ c := self peek.
     (c = $]) ] whileFalse: [
      (c = $,) ifTrue: [ self next. ].
      value := self nextJSONObject.
      obj add: value.
   ].
   self next.
   ^obj
!

nextJSONDict
   "I decode JSON objects from the self and will return a Dictionary containing all the key/value pairs."
   | c obj key value |
   obj := Dictionary new.
   self next.
   [ c := self peek.
     c = $} ] whileFalse: [
      (c = $,) ifTrue: [ self next ].

      key := self nextJSONString.

      c := self next.
      c = $: ifFalse: [
         self error: ('unexpected character found where name-seperator '':'' expected, found: %1' bindWith: c)
      ].

      value := self nextJSONObject.

      obj at: key put: value.
   ].
   self next.
   ^obj
!

nextJSONString
   "I'm extracting a JSON string from the self and return them as String."
   | c obj str |
   str := WriteStream on: (String new).
   self next.
   [
        c := self next.
        c = $"
   ] whileFalse: [
      c = $\
         ifTrue: [
            c := self next.
            c isNil ifTrue:
               [ ^self error: 'expected character, found end of self' ].
            c = $b ifTrue: [ c := 8 asCharacter ].
            c = $f ifTrue: [ c := 12 asCharacter ].
            c = $n ifTrue: [ c := Character nl ].
            c = $r ifTrue: [ c := Character cr ].
            c = $t ifTrue: [ c := Character tab ].
            c = $u
               ifTrue: [
                  c := (Integer readFrom: (self next: 4) readStream radix: 16) asCharacter.
                  (c class == UnicodeCharacter and: [ str species == String ])
                    ifTrue: [ str := (UnicodeString new writeStream
                                        nextPutAll: str contents; yourself) ] ].
         ].
      str nextPut: c.
   ].
   "Undo the conversion to UnicodeString done above."
   ^str contents asString
!

nextJSONNumber
   "I'm extracting a number in JSON format from the self and return Integer or Float depending on the input."
   | c num sgn int intexp frac exp isfloat |
   num := WriteStream on: (String new).

   isfloat := false.
   sgn     := 1.
   int     := 0.
   intexp  := 1.

   c := self peek.
   (c isNil) ifTrue: [ ^self error: 'expected number or -sign, but found end of self' ].
   c = $- ifTrue: [ sgn := -1. self next. ].

   c := self peek.
   (c isNil) ifTrue: [ ^self error: 'expected number, but found end of self' ].
   (c isDigit or: [ c = $. ]) ifFalse: [ ^self error: 'invalid JSON input' ].

   [ c notNil and: [ c isDigit ] ] whileTrue: [
      self next.
      int := sgn * (c digitValue) + (int * 10).
      c := self peek
   ].
   (c isNil) ifTrue: [ ^int ].

   c = $. ifTrue: [
      self next.
      isfloat := true.
      [ c := self peek. c notNil and: [ c isDigit ] ] whileTrue: [
         sgn := sgn / 10.
         int := sgn * (c digitValue) + int.
         self next
      ]
   ].

   exp := 0.
   ((c = $e) or: [ c = $E ]) ifTrue: [
      self next.
      c := self peek.
      (c isNil) ifTrue: [ ^int ].
      sgn := 1.
      c = $+ ifTrue: [ sgn :=  1. self next ].
      c = $- ifTrue: [ sgn := -1. self next ].

      [ c := self peek. c notNil and: [ c isDigit ] ] whileTrue: [
         exp := (c digitValue) + (exp * 10).
         self next
      ].

      int := int * (10 raisedToInteger: exp)
   ].

   ^int asFloat
! !

!Number methodsFor: 'json'!

jsonPrintOn: aStream
   "I return the Number in a JSON compatible format as String."
   self asFloat printOn: aStream
! !

!Float methodsFor: 'json'!

jsonPrintOn: aStream
   "I return the Number in a JSON compatible format as String."
   aStream nextPutAll:
        (self printString copyReplacing: self exponentLetter withObject: $e)
! !

!Integer methodsFor: 'json'!

jsonPrintOn: aStream
   "I return the Integer in a JSON compatible format as String."
   self printOn: aStream
! !

!Dictionary methodsFor: 'json'!

jsonPrintOn: ws
   "I encode my contents (key/value pairs) to a JSON object and return it as String."
   | f |
   ws nextPut: ${.
   f := true.
   self keysAndValuesDo: [ :key :val |
      f ifFalse: [ ws nextPut: $, ].
      key jsonPrintOn: ws.
      ws nextPut: $:.
      val jsonPrintOn: ws.
      f := false
   ].
   ws nextPut: $}.
! !

!CharacterArray methodsFor: 'json'!

jsonPrintOn: ws
   "I will encode me as JSON String and return a String containing my encoded version."
   ws nextPut: $".
   self do: [ :c || i |
      i := c asInteger.
      (((i = 16r20
         or: [ i = 16r21 ])
         or: [ i >= 16r23 and: [ i <= 16r5B ] ])
         or: [ i >= 16r5D ])
            ifTrue: [ ws nextPut: c ];
            ifFalse: [ | f |
               f := false.
               ws nextPut: $\.
               i = 16r22 ifTrue: [ f := true. ws nextPut: c ].
               i = 16r5C ifTrue: [ f := true. ws nextPut: c ].
               i = 16r2F ifTrue: [ f := true. ws nextPut: c ].
               i = 16r08 ifTrue: [ f := true. ws nextPut: $b ].
               i = 16r0C ifTrue: [ f := true. ws nextPut: $f ].
               i = 16r0A ifTrue: [ f := true. ws nextPut: $n ].
               i = 16r0D ifTrue: [ f := true. ws nextPut: $r ].
               i = 16r09 ifTrue: [ f := true. ws nextPut: $t ].
               f ifFalse: [
                  ws nextPut: $u.
                  ws nextPutAll: ('0000', i printString: 16) last: 4 ].
            ]
   ].
   ws nextPut: $".
!

!String methodsFor: 'json'!

jsonPrintOn: aStream
   "I will encode me as JSON String and return a String containing my encoded version."
   (self anySatisfy: [ :ch | ch value between: 128 and: 255 ])
        ifTrue: [ self asUnicodeString jsonPrintOn: aStream ]
        ifFalse: [ super jsonPrintOn: aStream ]! !

!SequenceableCollection methodsFor: 'json'!

jsonPrintOn: ws
   "I'm returning a JSON encoding of my contents as String."
   | f |
   ws nextPut: $[.
   f := true.
   self do: [ :val |
      f ifFalse: [ ws nextPut: $, ].
      val jsonPrintOn: ws.
      f := false
   ].
   ws nextPut: $].
   ^ws contents
! !

!UndefinedObject methodsFor: 'json'!

jsonPrintOn: aStream
   "I'm returning my corresponding value as JSON String."
   aStream nextPutAll: 'null'
! !

!Boolean methodsFor: 'json'!

jsonPrintOn: aStream
   "I'm returning the JSON String for truth or lie."
   self printOn: aStream
! !

!Object methodsFor: 'json'!

jsonPrintOn: aStream
    self subclassResponsibility
!

toJSON
    ^String streamContents: [ :aStream | self jsonPrintOn: aStream ]
! !


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Robin Redeker-2
On Wed, Aug 08, 2007 at 03:10:14PM +0200, Paolo Bonzini wrote:

>
> >   -  Maybe also the datatypes for json objects and arrays could be made
> >   configurable
> >      somehow. It's currently limited to generating Dictionary and
> >      OrderedCollection
> >      from a JSON string.
> >      The dumping works for Dictionaries and SequenceableCollection.
> >
> >So, before the code gets lost I've attached it to this mail (json.st).
> >
> >There are some comments in the code and I hope it's mostly understandable.
>
> I've fixed most issues except the one above.

Thanks alot! I guess the above is just a minor nice-to-have
feature. The other problems were more serious IMO.

> In addition, I changed your code so that: 1) for reading, the lexer
> *uses* a stream instead of subclassing it; the stream is in an instance
> variable, so that the reading happens in the instance side of the
> JSONReader class;

Sounds great, I'm not that used to smalltalk programming after all.

> 2) the writing is done to a stream, which eliminates
> the need to construct temporary streams in toJSON.

Yay!

> There was one bug: the code was subject to infinite loops in the
> presence of invalid JSON input such as '2.5d'.

Oh, thats bad.

> While I don't have time to make it into a real gst package, it will be
> done in due time (especially if somebody gets an idea on how to
> implement generating other classes than dictionaries).

Wouldn't it be enough to add some methods to JSONReader that return
objects for storing array and json-objects. eg. like:


   !JSONReader methodsFor: 'private'!

   make_array
      ^OrderedCollection new.

And then something like:

   !JSONReader methodsFor: 'private'~

   nextJSONArray
      | c obj value |
      obj := self make_array.
      ...

Then the objects returned by make_array and make_object have to
implement the right methods (interfaces of Collection and Dictionary
types should be enough).

There is another minor "issue", I've recently read the JSON spec
more carefully and saw that fromJSON only needs to parse a JSON object,
that means that fromJSON shouldn't accept a string like '4' or
'["fef"]', but only something like this '{"a":"b"}'.


Robin


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Robin Redeker-2
In reply to this post by Paolo Bonzini-2
On Wed, Aug 08, 2007 at 03:10:14PM +0200, Paolo Bonzini wrote:
[.snip.]
> >So, before the code gets lost I've attached it to this mail (json.st).
> >
> >There are some comments in the code and I hope it's mostly understandable.
>
> I've fixed most issues except the one above.
>
[.snip.]

> nextJSONNumber
>    "I'm extracting a number in JSON format from the self and return Integer or Float depending on the input."
[.snip.]
>    ^int asFloat
> ! !

I'm just curious: Is there a reason why this will only return floats now?


Robin


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Paolo Bonzini
> I'm just curious: Is there a reason why this will only return floats now?

I probably fixed this after posting the code.

Paolo


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Robin Redeker-2
On Sat, Aug 18, 2007 at 09:46:27PM +0200, Paolo Bonzini wrote:
> >I'm just curious: Is there a reason why this will only return floats now?
>
> I probably fixed this after posting the code.
>

Oh, ok, is there some place where the current code is?
I'm a bit back into smalltalk programming right now and would
need it now :-)

Btw. when I run your json.st through gst-convert I get a conversion of
this:
   !Object methodsFor: 'json'!

   jsonPrintOn: aStream
       self subclassResponsibility
   !

   toJSON
       ^String streamContents: [ :aStream | self jsonPrintOn: aStream ]
   ! !

To this:

   Object class extend [
       jsonPrintOn: aStream [
           <category: 'json'>
           self subclassResponsibility
       ]
       toJSON [
           <category: 'json'>
           ^String streamContents: [:aStream | self jsonPrintOn: aStream]
       ]
   ]

Is that intentional? Because adding a method to the class object
seems to be a bit weird (and also doesn't work when calling toJSON on
an instance of Object).

(I get those results with latest cvs and 2.95c)


Robin


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Paolo Bonzini

> Is that intentional? Because adding a method to the class object
> seems to be a bit weird (and also doesn't work when calling toJSON on
> an instance of Object).

It is indeed.  Are you sure it's still there on the latest CVS?

I attach the code I have.

Paolo


Stream subclass: #JSONReader
    instanceVariableNames: 'stream'
    classVariableNames: ''
    poolDictionaries: ''
    category: nil !

JSONReader comment:
'I read data structures (currently build of OrderedCollection and Dictionary)
from and to JSON (Java Script Object Notation). Writing is done with the
#toJSON method (note: it will behave badly with circular data structures).' !

!JSONReader class methodsFor: 'json'!

toJSON: anObject
   "I'm returning a JSON string which represents the object."
   ^anObject toJSON
!

fromJSON: string
   "I'm responsible for decoding the JSON string to objects."
   ^(self on: string readStream) nextJSONObject
!

on: aStream
    ^self new stream: aStream
! !

!JSONReader methodsFor: 'json'!

stream: aStream
    stream := aStream
!

peek
   "I'm peeking for the next non-whitespace character and will drop all whitespace in front of it"
   | c |
   [
     c := stream peek.
     c = (Character space)
         or: [ c = (Character tab)
         or: [ c = (Character lf)
         or: [ c = (Character cr)]]]
   ] whileTrue: [
     stream next
   ].
   ^c
!

next
   "I'm returning the next non-whitespace character"
   | c |
   c := self peek.
   c isNil ifTrue: [ ^self error: 'expected character but found end of stream' ].
   stream next.
   ^c
! !

!JSONReader methodsFor: 'private'!

nextJSONObject
   "I decode a json self to a value, which will be one of: nil,
true, false, OrderedCollection, Dictionary, String or Number
(i will return Integer or Float depending on the input)."
   | c |
   c := self peek.
   (c = $n) ifTrue: [ self next: 4. ^nil   ].
   (c = $t) ifTrue: [ self next: 4. ^true  ].
   (c = $f) ifTrue: [ self next: 5. ^false ].
   (c = ${) ifTrue: [ ^self nextJSONDict ].
   (c = $[) ifTrue: [ ^self nextJSONArray  ].
   (c = $") ifTrue: [ ^self nextJSONString ].
   ^self nextJSONNumber
!

nextJSONArray
   "I decode JSON arrays from self and will return a OrderedCollection for them."
   | c obj value |
   obj := OrderedCollection new.
   self next.
   [ c := self peek.
     (c = $]) ] whileFalse: [
      (c = $,) ifTrue: [ self next. ].
      value := self nextJSONObject.
      obj add: value.
   ].
   self next.
   ^obj
!

nextJSONDict
   "I decode JSON objects from self and will return a Dictionary containing all the key/value pairs."
   | c obj key value |
   obj := Dictionary new.
   self next.
   [ c := self peek.
     c = $} ] whileFalse: [
      (c = $,) ifTrue: [ self next ].

      key := self nextJSONString.

      c := self next.
      c = $: ifFalse: [
         self error: ('unexpected character found where name-seperator '':'' expected, found: %1' bindWith: c)
      ].

      value := self nextJSONObject.

      obj at: key put: value.
   ].
   self next.
   ^obj
!

nextJSONString
   "I'm extracting a JSON string from self and return them as String."
   | c obj str |
   str := WriteStream on: (String new).
   self next.
   [
        c := self next.
        c = $"
   ] whileFalse: [
      c = $\
         ifTrue: [
            c := self next.
            c isNil ifTrue:
               [ ^self error: 'expected character, found end of self' ].
            c = $b ifTrue: [ c := 8 asCharacter ].
            c = $f ifTrue: [ c := 12 asCharacter ].
            c = $n ifTrue: [ c := Character nl ].
            c = $r ifTrue: [ c := Character cr ].
            c = $t ifTrue: [ c := Character tab ].
            c = $u
               ifTrue: [
                  c := (Integer readFrom: (self next: 4) readStream radix: 16) asCharacter.
                  (c class == UnicodeCharacter and: [ str species == String ])
                    ifTrue: [ str := (UnicodeString new writeStream
                                        nextPutAll: str contents; yourself) ] ].
         ].
      str nextPut: c.
   ].
   "Undo the conversion to UnicodeString done above."
   ^str contents asString
!

nextJSONNumber
   "I'm extracting a number in JSON format from self and return Integer or Float depending on the input."
   | c num sgn int intexp frac exp isfloat |
   num := WriteStream on: (String new).

   isfloat := false.
   sgn     := 1.
   int     := 0.
   intexp  := 1.

   c := self peek.
   (c isNil) ifTrue: [ ^self error: 'expected number or -sign, but found end of self' ].
   c = $- ifTrue: [ sgn := -1. self next. ].

   c := self peek.
   (c isNil) ifTrue: [ ^self error: 'expected number, but found end of self' ].
   (c isDigit or: [ c = $. ]) ifFalse: [ ^self error: 'invalid JSON input' ].

   [ c notNil and: [ c isDigit ] ] whileTrue: [
      self next.
      int := sgn * (c digitValue) + (int * 10).
      c := self peek
   ].
   (c isNil) ifTrue: [ ^int ].

   c = $. ifTrue: [
      self next.
      isfloat := true.
      [ c := self peek. c notNil and: [ c isDigit ] ] whileTrue: [
         sgn := sgn / 10.
         int := sgn * (c digitValue) + int.
         self next
      ]
   ].

   exp := 0.
   ((c = $e) or: [ c = $E ]) ifFalse: [
        ^isfloat ifTrue: [ int asFloat ] ifFalse: [ int ] ].

   self next.
   c := self peek.
   (c isNil) ifTrue: [ ^int ].
   sgn := 1.
   c = $+ ifTrue: [ sgn :=  1. self next ].
   c = $- ifTrue: [ sgn := -1. self next ].

   [ c := self peek. c notNil and: [ c isDigit ] ] whileTrue: [
      exp := (c digitValue) + (exp * 10).
      self next
   ].

   int := int * (10 raisedToInteger: exp * sgn).
   ^int asFloat
! !

!Number methodsFor: 'json'!

jsonPrintOn: aStream
   "I return the Number in a JSON compatible format as String."
   self asFloat printOn: aStream
! !

!Float methodsFor: 'json'!

jsonPrintOn: aStream
   "I return the Number in a JSON compatible format as String."
   aStream nextPutAll:
        (self printString copyReplacing: self exponentLetter withObject: $e)
! !

!Integer methodsFor: 'json'!

jsonPrintOn: aStream
   "I return the Integer in a JSON compatible format as String."
   self printOn: aStream
! !

!Dictionary methodsFor: 'json'!

jsonPrintOn: ws
   "I encode my contents (key/value pairs) to a JSON object and return it as String."
   | f |
   ws nextPut: ${.
   f := true.
   self keysAndValuesDo: [ :key :val |
      f ifFalse: [ ws nextPut: $, ].
      key jsonPrintOn: ws.
      ws nextPut: $:.
      val jsonPrintOn: ws.
      f := false
   ].
   ws nextPut: $}.
! !

!CharacterArray methodsFor: 'json'!

jsonPrintOn: ws
   "I will encode me as JSON String and return a String containing my encoded version."
   ws nextPut: $".
   self do: [ :c || i |
      i := c asInteger.
      (((i = 16r20
         or: [ i = 16r21 ])
         or: [ i >= 16r23 and: [ i <= 16r5B ] ])
         or: [ i >= 16r5D ])
            ifTrue: [ ws nextPut: c ];
            ifFalse: [ | f |
               f := false.
               ws nextPut: $\.
               i = 16r22 ifTrue: [ f := true. ws nextPut: c ].
               i = 16r5C ifTrue: [ f := true. ws nextPut: c ].
               i = 16r2F ifTrue: [ f := true. ws nextPut: c ].
               i = 16r08 ifTrue: [ f := true. ws nextPut: $b ].
               i = 16r0C ifTrue: [ f := true. ws nextPut: $f ].
               i = 16r0A ifTrue: [ f := true. ws nextPut: $n ].
               i = 16r0D ifTrue: [ f := true. ws nextPut: $r ].
               i = 16r09 ifTrue: [ f := true. ws nextPut: $t ].
               f ifFalse: [
                  ws nextPut: $u.
                  ws nextPutAll: ('0000', i printString: 16) last: 4 ].
            ]
   ].
   ws nextPut: $".
!

!String methodsFor: 'json'!

jsonPrintOn: aStream
   "I will encode me as JSON String and return a String containing my encoded version."
   (self anySatisfy: [ :ch | ch value between: 128 and: 255 ])
        ifTrue: [ self asUnicodeString jsonPrintOn: aStream ]
        ifFalse: [ super jsonPrintOn: aStream ]! !

!SequenceableCollection methodsFor: 'json'!

jsonPrintOn: ws
   "I'm returning a JSON encoding of my contents as String."
   | f |
   ws nextPut: $[.
   f := true.
   self do: [ :val |
      f ifFalse: [ ws nextPut: $, ].
      val jsonPrintOn: ws.
      f := false
   ].
   ws nextPut: $].
   ^ws contents
! !

!UndefinedObject methodsFor: 'json'!

jsonPrintOn: aStream
   "I'm returning my corresponding value as JSON String."
   aStream nextPutAll: 'null'
! !

!Boolean methodsFor: 'json'!

jsonPrintOn: aStream
   "I'm returning the JSON String for truth or lie."
   self printOn: aStream
! !

!Object methodsFor: 'json'!

jsonPrintOn: aStream
    self subclassResponsibility
!

toJSON
    ^String streamContents: [ :aStream | self jsonPrintOn: aStream ]
! !


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: JSON Dumper/Parser

Paolo Bonzini-2
In reply to this post by Robin Redeker-2

>>> There are some comments in the code and I hope it's mostly understandable.
>> I've fixed most issues except the one above.
>
> Thanks alot! I guess the above is just a minor nice-to-have
> feature. The other problems were more serious IMO.

It will take time to implement it well (i.e. creating all kinds of
Smalltalk objects...).  Probably only after Magritte is ported to GST,
see www.lukas-renggli.ch/smalltalk/magritte for more info.

(There is a lurker, in this mailing list, that I'll meet next week...)

Paolo


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Robin Redeker-2
In reply to this post by Paolo Bonzini
On Sun, Aug 19, 2007 at 01:43:52PM +0200, Paolo Bonzini wrote:
>
> >Is that intentional? Because adding a method to the class object
> >seems to be a bit weird (and also doesn't work when calling toJSON on
> >an instance of Object).
>
> It is indeed.  Are you sure it's still there on the latest CVS?

Oh, forgot to look in CVS, it works with the latest CVS. So nvm :-)


Robin


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Robin Redeker-2
In reply to this post by Paolo Bonzini
On Sun, Aug 19, 2007 at 01:43:52PM +0200, Paolo Bonzini wrote:
> I attach the code I have.

Here is a small fix which fixes writing json directly to eg. files:

--- json.st     2007-09-20 13:02:49.000000000 +0200
+++ json.st.n   2007-09-20 13:02:19.000000000 +0200
@@ -290,7 +290,6 @@
       f := false
    ].
    ws nextPut: $].
-   ^ws contents
 ! !
 
 !UndefinedObject methodsFor: 'json'!


I guess returning the stream contents there is a left over from my old code.

Btw. do you have json.st somewhere in a repository on your side?
Maybe checking it in to the examples/ directory until it is packaged up
would be best?

You wrote earlier that if someone comes up with an idea how to generate
custom objects it would speed up packaging it, what about this:


--- json.st     2007-09-20 13:20:43.000000000 +0200
+++ json.st.n   2007-09-20 13:20:02.000000000 +0200
@@ -56,6 +56,20 @@
    ^c
 ! !
 
+!JSONReader methodsFor: 'object creation'!
+
+newJSONArrayObject
+   "I return an object which implements at least the add: method for storing
+    the elements of a JSON array."
+    ^OrderedCollection new.
+!
+
+newJSONObject
+   "I return a object which implements the method at:put: for storing
+    the key/value pairs of JSON objects."
+    ^Dictionary new.
+! !
+
 !JSONReader methodsFor: 'private'!
 
 nextJSONObject
@@ -76,7 +90,7 @@
 nextJSONArray
    "I decode JSON arrays from self and will return a OrderedCollection for them."
    | c obj value |
-   obj := OrderedCollection new.
+   obj := self newJSONArrayObject.
    self next.
    [ c := self peek.
      (c = $]) ] whileFalse: [
@@ -91,7 +105,7 @@
 nextJSONDict
    "I decode JSON objects from self and will return a Dictionary containing all the key/value pairs."
    | c obj key value |
-   obj := Dictionary new.
+   obj := self newJSONObject.
    self next.
    [ c := self peek.
      c = $} ] whileFalse: [



It would allow people to inherit from JSONReader and plug in their own
classes for storing json objects and arrays. Of course this doesn't work
for numbers, strings and true/false, etc. But I don't know if it makes much
sense to allow that much customisation.



Robin


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Paolo Bonzini

> --- json.st     2007-09-20 13:02:49.000000000 +0200
> +++ json.st.n   2007-09-20 13:02:19.000000000 +0200
> @@ -290,7 +290,6 @@
>        f := false
>     ].
>     ws nextPut: $].
> -   ^ws contents
>  ! !
>  
>  !UndefinedObject methodsFor: 'json'!
>
> I guess returning the stream contents there is a left over from my old code.

Yes, thanks.

> Btw. do you have json.st somewhere in a repository on your side?
> Maybe checking it in to the examples/ directory until it is packaged up
> would be best?

Yes, that's a good idea.

> It would allow people to inherit from JSONReader and plug in their own
> classes for storing json objects and arrays. Of course this doesn't work
> for numbers, strings and true/false, etc. But I don't know if it makes much
> sense to allow that much customisation.

I am not sure (yet) of the usefulness of this.  I would like to see
first how Magritte could be plugged into JSONReader/JSONWriter.

Paolo


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Robin Redeker-2
On Thu, Sep 20, 2007 at 02:53:59PM +0200, Paolo Bonzini wrote:
> >It would allow people to inherit from JSONReader and plug in their own
> >classes for storing json objects and arrays. Of course this doesn't work
> >for numbers, strings and true/false, etc. But I don't know if it makes much
> >sense to allow that much customisation.
>
> I am not sure (yet) of the usefulness of this.  I would like to see
> first how Magritte could be plugged into JSONReader/JSONWriter.

I've run the code of the magritte squeak package through gst-convert
now and solved some conflicts, there are however a few things that seem
to need some more serious work (haven't looked into these issues yet).

Do you by any chance already have ported the code somewhere? :-)

Just wanted to play around with it without needing to install squeak :)


Robin


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk
Reply | Threaded
Open this post in threaded view
|

Re: Re: JSON Dumper/Parser

Paolo Bonzini
Robin Redeker wrote:

> On Thu, Sep 20, 2007 at 02:53:59PM +0200, Paolo Bonzini wrote:
>>> It would allow people to inherit from JSONReader and plug in their own
>>> classes for storing json objects and arrays. Of course this doesn't work
>>> for numbers, strings and true/false, etc. But I don't know if it makes much
>>> sense to allow that much customisation.
>> I am not sure (yet) of the usefulness of this.  I would like to see
>> first how Magritte could be plugged into JSONReader/JSONWriter.
>
> I've run the code of the magritte squeak package through gst-convert
> now and solved some conflicts, there are however a few things that seem
> to need some more serious work (haven't looked into these issues yet).

Feel free to ask!

I suggest you start with these options:

   -r'MessageSend->DirectedMessage' \
   -r'(``@object ifNil: ``@arg ifNotNil: [ | `@t2 | `@.s2 ] )->
      (``@object ifNil: ``@arg ifNotNil: [ :foo || `@t2 | `@.s2 ])' \
   -r'(``@object ifNotNil: [ | `@t2 | `@.s2 ] ifNil: ``@arg )->
      (``@object ifNotNil: [ :foo || `@t2 | `@.s2 ] ifNil: ``@arg)' \
   -r'(``@object ifNotNil: [ | `@t2 | `@.s2 ] )->
      (``@object ifNotNil: [ :foo || `@t2 | `@.s2 ])' \
   -r'(``@object ifNil: ``@arg1 ifNotNilDo: ``@arg2 )->
      (``@object ifNil: ``@arg1 ifNotNil: ``@arg2)' \
   -r'(``@object ifNotNilDo: ``@arg2 ifNil: ``@arg1 )->
      (``@object ifNotNil: ``@arg2 ifNil: ``@arg1)' \
   -r'(``@object ifNotNilDo: ``@arg2 )->
      (``@object ifNotNil: ``@arg2)' \
   -r'(``@object doIfNotNil: ``@arg2 )->
      (``@object ifNotNil: ``@arg2)' \

(should probably add a database of standard rewrites to gst-convert?)

> Do you by any chance already have ported the code somewhere? :-)

No, I didn't. :-)

Paolo


_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk