I liked this quiz because it made me look into treetop, ragel and
some
other libraries I wanted to examine a little bit closer for quite a
while now. Anyway, for my (official) solution I took the easy road
and
rely on ruby to do the actual work.
My solution is ruby19 only, since I used the opportunity to explore
some
of the new regexp features. Since it uses eval(), there is a
possibility
for ruby code injection like the ultimatively relieving “#{sudo rm - rf /
}”. I think my solution catches such attacks though.
BTW, what do you all think should be the canonic output of the
following
JSON snippet:
json1 = <<JSON
{“a”:2,“b”:3.141,“TIME”:“2007-03-14T11:52:40”,“c”:“c”,“d”:[1,“b”,
3.14],“COUNT”:666,“e”:{“foo”:“bar”},“foo”:“B\u00e4r”,“g”:“\u677e
\u672c\u884c\u5f18”,“h”:1000.0,“bar”:“\u00a9 \u2260 \u20ac!”,“i”:
0.001,“j”:“\ud840\udc01”}
JSON
I get conflicting results between various versions of my solution and
the official ruby19 parser with respect to these utf characters. This
snippet is taken (IIRC) from the ruby-json parser.
Regards,
Thomas.
#!/usr/bin/env ruby19
Author:: Thomas Link (micathom AT gmail com)
Created:: 2008-02-01.
The string (in JSON format) is tokenized and pre-validated. Minor
replacements are made in order to transform the JSON into valid
ruby
input. The transformed string is then evaluated by ruby, which will
throw an exception on syntactic errors.
PROBLEMS:
- The “parser” doesn’t per se detect something like {“foo”: 1,} or
[1,2,] since this is valid in ruby. I’m not sure about JSON. Anyway,
I
included another “invalid” clause in order to catch these cases of
which I’m not sure how they are handled properly. If you want the
parser to be more permissive, remove the first “invalid” clause.
REFERENCES:
http://json.org
http://www.ietf.org/rfc/rfc4627.txt
class JSONParser
RXE = /
\[|\]|
\{|\}|
(?<name_sep>:)|
(?<invalid>,\s*[}\]])|
,|
(?<string>"([^"\\]++|\\(u[0-9a-fA-F]{4}|[bfnrt"\/\\]))*")|
-?(0|[1-9]\d*+)(\.\d++)?([Ee][+-]?\d++)?(?=\D|$)|
true|
false|
(?<null>null)|
[[:space:][:cntrl:]]++|
(?<invalid>.++)
/xmu
def parse(json)
ruby = json.gsub(RXE) do |t|
m = $~
if m['invalid'] then invalid(m['invalid'])
elsif m['null'] then 'nil'
elsif m['name_sep'] then '=>'
elsif m['string'] then m['string'].gsub(/#/, '\\\\#')
else
t
end
end
begin
return eval(ruby)
rescue Exception => e
invalid(json)
end
end
def invalid(string)
raise RuntimeError, 'Invalid JSON: %s' % string
end
end
if FILE == $0
a = ARGV.join
p a
p JSONParser.new.parse(a)
end