forked from erikrose/parsimonious
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add JSON decoder example (see erikrose#18)
- Loading branch information
Vladimir Keleshev
committed
Apr 7, 2013
1 parent
bf7376c
commit f951451
Showing
1 changed file
with
112 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
r"""# JSON grammar | ||
value = space (string / number / object / array / true_false_null) space | ||
object = "{" members "}" | ||
members = (pair ("," pair)*)? | ||
pair = string ":" value | ||
array = "[" elements "]" | ||
elements = (value ("," value)*)? | ||
true_false_null = "true" / "false" / "null" | ||
string = space "\"" char* "\"" space | ||
char = ~"[^\"]" # TODO implement the real thing | ||
number = (int frac exp) / (int exp) / (int frac) / int | ||
int = "-"? ((digit1to9 digits) / digit) | ||
frac = "." digits | ||
exp = e digits | ||
digits = digit+ | ||
e = "e+" / "e-" / "e" / "E+" / "E-" / "E" | ||
digit1to9 = ~"[1-9]" | ||
digit = ~"[0-9]" | ||
space = ~"\s*" | ||
""" | ||
from parsimonious.grammar import Grammar | ||
|
||
|
||
class JSONDecoder(object): | ||
|
||
def loads(self, node): | ||
if isinstance(node, str): | ||
node = Grammar(__doc__).parse(node) | ||
method = getattr(self, node.expr_name, self.default) | ||
return method(node, [self.loads(n) for n in node]) | ||
|
||
def default(self, node, children): | ||
return children | ||
|
||
def value(self, node, children): | ||
return children[1][0] | ||
|
||
def object(self, node, children): | ||
return children[1] | ||
|
||
def members(self, node, children): | ||
if not children: | ||
return {} | ||
head, tail = children[0][:1], children[0][1:][0] | ||
return dict(head + [el[1] for el in tail]) | ||
|
||
def pair(self, node, children): | ||
return children[0], children[2] | ||
|
||
def array(self, node, children): | ||
return children[1] | ||
|
||
def elements(self, node, children): | ||
if not children: | ||
return [] | ||
head, tail = children[0][:1], children[0][1:][0] | ||
return head + [el[1] for el in tail] | ||
|
||
def true_false_null(self, node, children): | ||
return {'true': True, 'false': False, 'null': None}[node.text.strip()] | ||
|
||
def number(self, node, children): | ||
return float(node.text.strip()) | ||
|
||
def string(self, node, children): | ||
return node.text.strip().strip('"') # TODO implement the real thing | ||
|
||
|
||
json = JSONDecoder() | ||
|
||
|
||
def test_numbers(): | ||
assert json.loads('5') == 5 | ||
assert json.loads('55') == 55 | ||
assert json.loads('-5') == -5 | ||
assert json.loads('5.5') == 5.5 | ||
assert json.loads('5e-2') == 5e-2 | ||
assert json.loads('5.3e-2') == 5.3e-2 | ||
|
||
|
||
def test_strings(): | ||
assert json.loads('""') == '' | ||
assert json.loads('"hai"') == 'hai' | ||
|
||
|
||
def test_true_false_null(): | ||
assert json.loads('true') == True | ||
assert json.loads('false') == False | ||
assert json.loads('null') == None | ||
|
||
|
||
def test_arrays(): | ||
assert json.loads('[]') == [] | ||
assert json.loads('[1]') == [1] | ||
assert json.loads('["a",1,2,3]') == ['a', 1, 2, 3] | ||
assert json.loads('["a",[1,2],3]') == ['a', [1, 2], 3] | ||
|
||
|
||
def test_objects(): | ||
assert json.loads('{}') == {} | ||
assert json.loads('{"a":1}') == {'a': 1} | ||
assert json.loads('{"a":1,"b":2}') == {'a': 1, 'b': 2} | ||
|
||
|
||
def test_whitespace(): | ||
source = ' { "a" : 1 , "b" : [ 2 , 3 ] } ' | ||
assert json.loads(source) == {'a': 1, 'b': [2, 3]} |