# = json - JSON library for Ruby # # == Description # # == Author # # Florian Frank # # == License # # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License Version 2 as published by the Free # Software Foundation: www.gnu.org/copyleft/gpl.html # # == Download # # The latest version of this library can be downloaded at # # * http://rubyforge.org/frs?group_id=953 # # Online Documentation should be located at # # * http://json.rubyforge.org # # == Examples # # To create a JSON string from a ruby data structure, you # can call JSON.unparse like that: # # json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10] # # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" # # It's also possible to call the #to_json method directly. # # json = [1, 2, {"a"=>3.141}, false, true, nil, 4..10].to_json # # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" # # To get back a ruby data structure, you have to call # JSON.parse on the JSON string: # # JSON.parse json # # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"] # # Note, that the range from the original data structure is a simple # string now. The reason for this is, that JSON doesn't support ranges # or arbitrary classes. In this case the json library falls back to call # Object#to_json, which is the same as #to_s.to_json. # # It's possible to extend JSON to support serialization of arbitray classes by # simply implementing a more specialized version of the #to_json method, that # should return a JSON object (a hash converted to JSON with #to_json) # like this (don't forget the *a for all the arguments): # # class Range # def to_json(*a) # { # 'json_class' => self.class.name, # 'data' => [ first, last, exclude_end? ] # }.to_json(*a) # end # end # # The hash key 'json_class' is the class, that will be asked to deserialize the # JSON representation later. In this case it's 'Range', but any namespace of # the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be # used to store the necessary data to configure the object to be deserialized. # # If a the key 'json_class' is found in a JSON object, the JSON parser checks # if the given class responds to the json_create class method. If so, it is # called with the JSON object converted to a Ruby hash. So a range can # be deserialized by implementing Range.json_create like this: # # class Range # def self.json_create(o) # new(*o['data']) # end # end # # Now it possible to serialize/deserialize ranges as well: # # json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10] # # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" # JSON.parse json # # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] # # JSON.unparse always creates the shortes possible string representation of a # ruby data structure in one line. This good for data storage or network # protocols, but not so good for humans to read. Fortunately there's # also JSON.pretty_unparse that creates a more readable output: # # puts JSON.pretty_unparse([1, 2, {"a"=>3.141}, false, true, nil, 4..10]) # [ # 1, # 2, # { # "a": 3.141 # }, # false, # true, # null, # { # "json_class": "Range", # "data": [ # 4, # 10, # false # ] # } # ] # # There are also the methods Kernel#j for unparse, and Kernel#jj for # pretty_unparse output to the console, that work analogous to Kernel#p and # Kernel#pp. # require 'strscan' # This module is the namespace for all the JSON related classes. It also # defines some module functions to expose a nicer API to users, instead # of using the parser and other classes directly. module JSON # The base exception for JSON errors. JSONError = Class.new StandardError # This exception is raise, if a parser error occurs. ParserError = Class.new JSONError # This exception is raise, if a unparser error occurs. UnparserError = Class.new JSONError # If a circular data structure is encountered while unparsing # this exception is raised. CircularDatastructure = Class.new UnparserError class << self # Switches on Unicode support, if _enable_ is _true_. Otherwise switches # Unicode support off. def support_unicode=(enable) @support_unicode = enable end # Returns _true_ if JSON supports unicode, otherwise _false_ is returned. # # If loading of the iconv library fails, or it doesn't support utf8/utf16 # encoding, this will be set to false, as a fallback. def support_unicode? !!@support_unicode end end JSON.support_unicode = true # default, however it's possible to switch off # full unicode support, if non-ascii bytes should be # just passed through. begin require 'iconv' # An iconv instance to convert from UTF8 to UTF16 Big Endian. UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be') # An iconv instance to convert from UTF16 Big Endian to UTF8. UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8'); UTF8toUTF16.iconv('no bom') rescue Errno::EINVAL begin old_verbose = $VERBOSE $VERBOSE = nil # An iconv instance to convert from UTF8 to UTF16 Big Endian. UTF16toUTF8 = Iconv.new('utf-8', 'utf-16') # An iconv instance to convert from UTF16 Big Endian to UTF8. UTF8toUTF16 = Iconv.new('utf-16', 'utf-8'); UTF8toUTF16.iconv('no bom') if UTF8toUTF16.iconv("\xe2\x82\xac") == "\xac\x20" swapper = Class.new do def initialize(iconv) @iconv = iconv end def iconv(string) result = @iconv.iconv(string) JSON.swap!(result) end end UTF8toUTF16 = swapper.new(UTF8toUTF16) end if UTF16toUTF8.iconv("\xac\x20") == "\xe2\x82\xac" swapper = Class.new do def initialize(iconv) @iconv = iconv end def iconv(string) string = JSON.swap!(string.dup) @iconv.iconv(string) end end UTF16toUTF8 = swapper.new(UTF16toUTF8) end rescue Errno::EINVAL # Enforce disabling of unicode support, if iconv doesn't support # UTF8/UTF16 at all. JSON.support_unicode = false ensure $VERBOSE = old_verbose end rescue LoadError # Enforce disabling of unicode support, if iconv doesn't exist. JSON.support_unicode = false end # Swap consecutive bytes in string in place. def self.swap!(string) 0.upto(string.size / 2) do |i| break unless string[2 * i + 1] string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i] end string end # This class implements the JSON parser that is used to parse a JSON string # into a Ruby data structure. class Parser < StringScanner STRING = /"((?:[^"\\]|\\.)*)"/ INTEGER = /-?\d+/ FLOAT = /-?\d+\.(\d*)(?i:e[+-]?\d+)?/ OBJECT_OPEN = /\{/ OBJECT_CLOSE = /\}/ ARRAY_OPEN = /\[/ ARRAY_CLOSE = /\]/ PAIR_DELIMITER = /:/ COLLECTION_DELIMITER = /,/ TRUE = /true/ FALSE = /false/ NULL = /null/ IGNORE = %r( (?: //[^\n\r]*[\n\r]| # line comments /\* # c-style comments (?: [^*/]| # normal chars /[^*]| # slashes that do not start a nested comment \*[^/]| # asterisks that do not end this comment /(?=\*/) # single slash before this comment's end )* \*/ # the end of this comment |\s+ # whitespaces )+ )mx UNPARSED = Object.new # Parses the current JSON string and returns the complete data structure # as a result. def parse reset until eos? case when scan(ARRAY_OPEN) return parse_array when scan(OBJECT_OPEN) return parse_object when skip(IGNORE) ; when !((value = parse_value).equal? 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(%r(\\(?:[\\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 '"' when '\\/' then '/' else if JSON.support_unicode? and $KCODE == 'UTF8' JSON.utf16_to_utf8($~[1]) else # if utf8 mode is switched off or unicode not supported, try to # transform unicode \u-notation to bytes directly: $~[1].to_i(16).chr end 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 skip(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 skip(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 skip(IGNORE) unless scan(PAIR_DELIMITER) raise ParserError, "expected ':' in object at '#{peek(20)}'!" end skip(IGNORE) unless (value = parse_value).equal? UNPARSED result[string] = value skip(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) if klassname = result['json_class'] klass = klassname.sub(/^:+/, '').split(/::/).inject(Object) do |p,k| p.const_get(k) rescue nil end break unless klass and klass.json_creatable? result = klass.json_create(result) end break when skip(IGNORE) ; else raise ParserError, "unexpected token in object at '#{peek(20)}'!" end end result end end # This class is used to create State instances, that are use to hold data # while unparsing a Ruby data structure into a JSON string. class State # Creates a State object from _opts_, which ought to be Hash to create a # new State instance configured by opts, something else to create an # unconfigured instance. If _opts_ is a State object, it is just returned. def self.from_state(opts) case opts when self opts when Hash new(opts) else new end end # Instantiates a new State object, configured by _opts_. def initialize(opts = {}) @indent = opts[:indent] || '' @space = opts[:space] || '' @object_nl = opts[:object_nl] || '' @array_nl = opts[:array_nl] || '' @seen = {} end # This string is used to indent levels in the JSON string. attr_accessor :indent # This string is used to include a space between the tokens in a JSON # string. attr_accessor :space # This string is put at the end of a line that holds a JSON object (or # Hash). attr_accessor :object_nl # This string is put at the end of a line that holds a JSON array. attr_accessor :array_nl # Returns _true_, if _object_ was already seen during this Unparsing run. def seen?(object) @seen.key?(object.__id__) end # Remember _object_, to find out if it was already encountered (to find out # if a cyclic data structure is unparsed). def remember(object) @seen[object.__id__] = true end # Forget _object_ for this Unparsing run. def forget(object) @seen.delete object.__id__ end end module_function # Convert _string_ from UTF8 encoding to UTF16 (big endian) encoding and # return it. def utf8_to_utf16(string) JSON::UTF8toUTF16.iconv(string).unpack('H*')[0] end # Convert _string_ from UTF16 (big endian) encoding to UTF8 encoding and # return it. def utf16_to_utf8(string) bytes = '' << string[0, 2].to_i(16) << string[2, 2].to_i(16) JSON::UTF16toUTF8.iconv(bytes) end # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with # UTF16 big endian characters as \u????, and return it. def utf8_to_json(string) i, n, result = 0, string.size, '' while i < n char = string[i] case when char == ?\b then result << '\b' when char == ?\t then result << '\t' when char == ?\n then result << '\n' when char == ?\f then result << '\f' when char == ?\r then result << '\r' when char == ?" then result << '\"' when char == ?\\ then result << '\\\\' when char == ?/ then result << '\/' when char.between?(0x0, 0x1f) then result << "\\u%04x" % char when char.between?(0x20, 0x7f) then result << char when !(JSON.support_unicode? && $KCODE == 'UTF8') # if utf8 mode is switched off or unicode not supported, just pass # bytes through: result << char when char & 0xe0 == 0xc0 result << '\u' << utf8_to_utf16(string[i, 2]) i += 1 when char & 0xf0 == 0xe0 result << '\u' << utf8_to_utf16(string[i, 3]) i += 2 when char & 0xf8 == 0xf0 result << '\u' << utf8_to_utf16(string[i, 4]) i += 3 when char & 0xfc == 0xf8 result << '\u' << utf8_to_utf16(string[i, 5]) i += 4 when char & 0xfe == 0xfc result << '\u' << utf8_to_utf16(string[i, 6]) i += 5 else raise JSON::UnparserError, "Encountered unknown UTF-8 byte: %x!" % char end i += 1 end result end # Parse the JSON string _source_ into a Ruby data structure and return it. def parse(source) Parser.new(source).parse end # Unparse the Ruby data structure _obj_ into a single line JSON string and # return it. _state_ is a JSON::State object, that can be used to configure # the output further. def unparse(obj, state = nil) obj.to_json(JSON::State.from_state(state)) end # Unparse the Ruby data structure _obj_ into a JSON string and return it. # The returned string is a prettier form of the string returned by #unparse. def pretty_unparse(obj) state = JSON::State.new( :indent => ' ', :space => ' ', :object_nl => "\n", :array_nl => "\n" ) obj.to_json(state) end end class Object # Converts this object to a string (calling #to_s), converts # it to a JSON string, and returns the result. This is a fallback, if no # special method #to_json was defined for some object. # _state_ is a JSON::State object, that can also be used # to configure the produced JSON string output further. def to_json(*) to_s.to_json end end class Hash # Returns a JSON string containing a JSON object, that is unparsed from # this Hash instance. # _state_ is a JSON::State object, that can also be used to configure the # produced JSON string output further. # _depth_ is used to find out nesting depth, to indent accordingly. 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 data structures 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 << state.object_nl if state result << map { |key,value| json_shift(state, depth + 1) << key.to_s.to_json(state, depth + 1) << ':' << state.space << 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 # Returns a JSON string containing a JSON array, that is unparsed from # this Array instance. # _state_ is a JSON::State object, that can also be used to configure the # produced JSON string output further. # _depth_ is used to find out nesting depth, to indent accordingly. 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 data structures 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 << 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 Integer # Returns a JSON string representation for this Integer number. def to_json(*) to_s end end class Float # Returns a JSON string representation for this Float number. def to_json(*) to_s end end class String # This string should be encoded with UTF-8 (if JSON unicode support is # enabled). A call to this method returns a JSON string # encoded with UTF16 big endian characters as \u????. If # JSON.support_unicode? is false only control characters are encoded this # way, all 8-bit bytes are just passed through. def to_json(*) '"' << JSON::utf8_to_json(self) << '"' end # Raw Strings are JSON Objects (the raw bytes are stored in an array for the # key "raw"). The Ruby String can be created by this class method. def self.json_create(o) o['raw'].pack('C*') end # This method creates a raw object, that can be nested into other data # structures and will be unparsed as a raw string. def to_json_raw_object { 'json_class' => self.class.name, 'raw' => self.unpack('C*'), } end # This method should be used, if you want to convert raw strings to JSON # instead of UTF-8 strings, e. g. binary data (and JSON Unicode support is # enabled). def to_json_raw(*args) to_json_raw_object.to_json(*args) end end class TrueClass # Returns a JSON string for true: 'true'. def to_json(*) to_s end end class FalseClass # Returns a JSON string for false: 'false'. def to_json(*) to_s end end class NilClass # Returns a JSON string for nil: 'null'. def to_json(*) 'null' end end module Kernel # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in # one line. def j(*objs) objs.each do |obj| puts JSON::unparse(obj) end nil end # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with # indentation and over many lines. def jj(*objs) objs.each do |obj| puts JSON::pretty_unparse(obj) end nil end end class Class # Returns true, if this class can be used to create an instance # from a serialised JSON string. The class has to implement a class # method _json_create_ that expects a hash as first parameter, which includes # the required data. def json_creatable? respond_to?(:json_create) end end # vim: set et sw=2 ts=2: