From: Florian Frank Date: 2005-01-28T06:58:12+09:00 Subject: Re: Any reasonably complete JSON-RPC ORB projects? --------------000307050102030202010203 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit ruby talk wrote: >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[:object_nl] || '' @array_nl = opts[:array_nl] || '' @seen = {} end attr_accessor :indent attr_accessor :object_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 => ' ', :object_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::pretty_unparse(obj) end nil end end # vim: set et sw=2 ts=2: --------------000307050102030202010203--