--------------000307050102030202010203
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit
ruby said:
Mixed in with the current churn about JavaScript and XmlHttpRequest
stuff is JSON [0] and JSON-RPC [1]. I've done some assorted RPC stuff
with XmlHttpRequest, but the JSON part looks interesting because it
transfers fewer bytes and may make it easier to build "standardized"
browser services in Ruby. (JSON is JavaScript object notation; like
YAML with C curly-braces instead of Python indentation, and JSON-RPC
is like XML-RPC, but with JSON.)
I've recently written a JSON parser based on Ruby's StringScanner class,
that doesn't support the unicode stuff of the specifcation fully yet. A
few small examples how it can be used come here:
puts JSON.pretty_unparse([1,{"foo"=>2,"bar"=>[true,false,nil]},"baz",3.14])
[
1,
{
"foo":2,
"bar":[
true,
false,
null
]
},
"baz",
3.14
]
p JSON.parse([1,{"foo"=>2,"bar"=>[true,false,nil]},"baz",3.14].to_json)
#=> [1, {"foo"=>2, "bar"=>[true, false, nil]}, "baz", 3.14]
I have attached a copy of the core class to this mail, but I wonder, if
I should do a real release (with tests and stuff) on Rubyforge, if you
or other people want to build something bigger with it.
--
Florian Frank
--------------000307050102030202010203
Content-Type: text/plain;
name="json.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="json.rb"
require 'strscan'
module JSON
JSONError = Class.new StandardError
ParserError = Class.new JSONError
CircularDatastructure = Class.new JSONError
class Parser < StringScanner
STRING = /"((?:\\"|[^"])*)"/
INTEGER = /\d+/
FLOAT = /-?\d+\.(\d*)(?:e[+-]?\d+)?/i
OBJECT_OPEN = /\{/
OBJECT_CLOSE = /\}/
ARRAY_OPEN = /\[/
ARRAY_CLOSE = /\]/
PAIR_DELIMITER = /:/
COLLECTION_DELIMITER = /,/
TRUE = /true/
FALSE = /false/
NULL = /null/
IGNORE = /\s+/
UNPARSED = Object.new
def parse
reset
until eos?
case
when scan(ARRAY_OPEN)
return parse_array
when scan(OBJECT_OPEN)
return parse_object
when scan(IGNORE)
;
when (value = parse_value) != UNPARSED
return value
else
raise ParserError, "source '#{peek(20)}' not in JSON!"
end
end
end
private
def parse_string
if scan(STRING)
return '' if self[1].empty?
self[1].gsub(/\\(?:[\\bfnrt"]|u([A-Fa-f\d]{4}))/) do
case $~[0]
when '\\\\' then '\\'
when '\\b' then "\b"
when '\\f' then "\f"
when '\\n' then "\n"
when '\\r' then "\r"
when '\\t' then "\t"
when '\"' then '"'
else $~[1].to_i(16).chr # TODO atm only unicode characters <= 0x127
end
end
else
UNPARSED
end
end
def parse_value
case
when scan(FLOAT)
Float(self[0])
when scan(INTEGER)
Integer(self[0])
when scan(TRUE)
true
when scan(FALSE)
false
when scan(NULL)
nil
when (string = parse_string) != UNPARSED
string
when scan(ARRAY_OPEN)
parse_array
when scan(OBJECT_OPEN)
parse_object
else
UNPARSED
end
end
def parse_array
result = []
until eos?
case
when (value = parse_value) != UNPARSED
result << value
scan(IGNORE)
unless scan(COLLECTION_DELIMITER) or match?(ARRAY_CLOSE)
raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
end
when scan(ARRAY_CLOSE)
break
when scan(IGNORE)
;
else
raise ParserError, "unexpected token in array at '#{peek(20)}'!"
end
end
result
end
def parse_object
result = {}
until eos?
case
when (string = parse_string) != UNPARSED
scan(IGNORE)
unless scan(PAIR_DELIMITER)
raise ParserError, "expected ':' in object at '#{peek(20)}'!"
end
scan(IGNORE)
if (value = parse_value) != UNPARSED
result[string] = value
scan(IGNORE)
unless scan(COLLECTION_DELIMITER) or match?(OBJECT_CLOSE)
raise ParserError,
"expected ',' or '}' in object at '#{peek(20)}'!"
end
else
raise ParserError, "expected value in object at '#{peek(20)}'!"
end
when scan(OBJECT_CLOSE)
break
when scan(IGNORE)
;
else
raise ParserError, "unexpected token in object at '#{peek(20)}'!"
end
end
result
end
end
class State
def self.from_state(opts)
case opts
when self
opts
when Hash
new(opts)
else
new
end
end
def initialize(opts = {})
@indent = opts[:indent] || ''
@object_nl = opts[
bject_nl] || ''
@array_nl = opts[:array_nl] || ''
@seen = {}
end
attr_accessor :indent
attr_accessor
bject_nl
attr_accessor :array_nl
def seen?(object)
@seen.key?(object.__id__)
end
def remember(object)
@seen[object.__id__] = true
end
def forget(object)
@seen.delete object.__id__
end
end
module_function
def parse(source)
Parser.new(source).parse
end
def unparse(obj, state = nil)
state = JSON::State.from_state(state)
obj.to_json(state)
end
def pretty_unparse(obj)
state = JSON::State.new(
:indent => ' ',
bject_nl => "\n",
:array_nl => "\n"
)
obj.to_json(state)
end
end
class Object
def to_json(*args)
to_s.to_json
end
end
class Hash
def to_json(state = nil, depth = 0)
state = JSON::State.from_state(state)
json_check_circular(state) { json_transform(state, depth) }
end
private
def json_check_circular(state)
if state
state.seen?(self) and raise JSON::CircularDatastructure,
"circular datastructures not supported!"
state.remember self
end
yield
ensure
state and state.forget self
end
def json_shift(state, depth)
state and not state.object_nl.empty? or return ''
state.indent * depth
end
def json_transform(state, depth)
delim = ','
delim << state.object_nl if state
result = ''
result << '{'
result << state.object_nl if state
result << map { |string,value|
json_shift(state, depth + 1) <<
string.to_json(state, depth + 1) <<
':' << value.to_json(state, depth + 1)
}.join(delim)
result << state.object_nl if state
result << json_shift(state, depth)
result << '}'
result
end
end
class Array
def to_json(state = nil, depth = 0)
state = JSON::State.from_state(state)
json_check_circular(state) { json_transform(state, depth) }
end
private
def json_check_circular(state)
if state
state.seen?(self) and raise JSON::CircularDatastructure,
"circular datastructures not supported!"
state.remember self
end
yield
ensure
state and state.forget self
end
def json_shift(state, depth)
state and not state.array_nl.empty? or return ''
state.indent * depth
end
def json_transform(state, depth)
delim = ','
delim << state.array_nl if state
result = ''
result = '['
result << state.array_nl if state
result << map { |value|
json_shift(state, depth + 1) << value.to_json(state, depth + 1)
}.join(delim)
result << state.array_nl if state
result << json_shift(state, depth)
result << ']'
result
end
end
class Fixnum
def to_json(*args) to_s end
end
class Float
def to_json(*args) to_s end
end
class String
def to_json(*args)
'"' << gsub(/["\\\0-\037]/) do
case $~[0]
when '"' then '\\"'
when '\\' then '\\\\'
when "\b" then '\b'
when "\f" then '\f'
when "\n" then '\n'
when "\r" then '\r'
when "\t" then '\t'
else "\\u\%04x" % $~[0][0]
end
end << '"'
end
end
class TrueClass
def to_json(*args) to_s end
end
class FalseClass
def to_json(*args) to_s end
end
class NilClass
def to_json(*args) 'null' end
end
module Kernel
def j(*objs)
objs.each do |obj|
puts JSON::unparse(obj)
end
nil
end
def jj(*objs)
objs.each do |obj|
puts JSON:
retty_unparse(obj)
end
nil
end
end
# vim: set et sw=2 ts=2:
--------------000307050102030202010203--