Here is my solution, which, like steve's, uses Treetop. This is my first attempt at using Treetop, so I'm not certain how good the grammar file that I created is. steve's grammar file is smaller than mine is. When I benchmarked my code, I'm getting a parsing performance of around 10,000 chars/second, much lower than many of the other solutions. The two components of the solution are below -- a small Ruby class (that acts as an interface b/w James' unit testing and what Treetop produces) and a Treetop grammar file. Eric ==== Here's the small Ruby class: # A solution to RubyQuiz #155. # # Takes a JSON string and parses it into an equivalent Ruby value. # # See http://www.rubyquiz.com/quiz155.html for details. # # The latest version of this solution can also be found at # http://learnruby.com/examples/ruby-quiz-155.shtml . require 'rubygems' require 'treetop' Treetop.load 'json' class JSONParser def initialize @parser = JSONHelperParser.new end def parse(input) result = @parser.parse(input) raise "could not parse" if result.nil? result.resolve end end ==== And here's the Treetop grammar: # Treetop grammar for JSON. Note: it doesn't handle Unicode very well. grammar JSONHelper rule value spaces v:(simple_literal / string / number / array / object) spaces { def resolve v.resolve end } end rule spaces [ \t\n\r]* end # SIMPLE LITERALS rule simple_literal ("true" / "false" / "null") { def resolve case text_value when "true" : true when "false" : false when "null" : nil end end } end # NUMBERS rule number integer fractional:fractional? exponent:exponent? { def resolve if fractional.text_value.empty? && exponent.text_value.empty? integer.text_value.to_i else text_value.to_f end end } end rule integer "-"? [1-9] digits / # single digit "-"? [0-9] end rule fractional "." digits end rule exponent [eE] [-+]? digits end rule digits [0-9]* end # STRINGS rule string "\"\"" { def resolve "" end } / "\"" characters:character* "\"" { def resolve characters.elements.map { |c| c.resolve }.join end } end rule character # regular characters (!"\"" !"\\" .)+ { def resolve text_value end } / # escaped: \\, \", and \/ "\\" char:("\"" / "\\" / "/") { def resolve char.text_value end } / # escaped: \b, \f, \n, \r, and \t "\\" char:[bfnrt] { def resolve case char.text_value when 'b' : "\b" when 'f' : "\f" when 'n' : "\n" when 'r' : "\r" when 't' : "\t" end end } / # for Unicode that overlay ASCII values, we just use the ASCII value "\\u00" digits:(hex_digit hex_digit) { def resolve str = " " str[0] = digits.text_value.hex str end } / # for all other Unicode values use the null character "\\u" digits1:(hex_digit hex_digit) digits2:(hex_digit hex_digit) { def resolve str = " " str[0] = digits1.textvalue.hex str[1] = digits2.textvalue.hex str end } end rule hex_digit [0-9a-fA-F] end # ARRAYS rule array "[" spaces "]" { def resolve Array.new end } / "[" spaces value_list spaces "]" { def resolve value_list.resolve end } end rule value_list value !(spaces ",") { def resolve [ value.resolve ] end } / value spaces "," spaces value_list { def resolve value_list.resolve.unshift(value.resolve) end } end # OBJECTS rule object "{" spaces "}" { def resolve Hash.new end } / "{" spaces pair_list spaces "}" { def resolve pair_list.resolve end } end rule pair_list pair !(spaces ",") { def resolve { pair.resolve[0] => pair.resolve[1] } end } / pair spaces "," spaces pair_list { def resolve hash = pair_list.resolve hash[pair.resolve[0]] = pair.resolve[1] hash end } end rule pair string spaces ":" spaces value { def resolve [ string.resolve, value.resolve ] end } end end