Skip to content

Commit e18659d

Browse files
committed
Convert JSONObject into a Python OrderedDict
1 parent 9677345 commit e18659d

File tree

7 files changed

+341
-3
lines changed

7 files changed

+341
-3
lines changed

Diff for: json.py

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# -*- coding: utf-8 -*-
2+
# This file is part of the pymfony package.
3+
#
4+
# (c) Alexandre Quercia <[email protected]>
5+
#
6+
# For the full copyright and license information, please view the LICENSE
7+
# file that was distributed with this source code.
8+
9+
from __future__ import absolute_import;
10+
11+
import sys;
12+
13+
if sys.version_info <= (2, 6):
14+
from pymfony.component.system.py26.json import *;
15+
elif sys.version_info < (3, 0):
16+
from pymfony.component.system.py2.json import *;
17+
else:
18+
from pymfony.component.system.py3.json import *;
19+
20+
"""
21+
"""
22+
23+
class JSONDecoderOrderedDict(AbstractJSONDecoderOrderedDict):
24+
"""Simple JSON <http://json.org> decoder
25+
26+
Performs the following translations in decoding by default:
27+
28+
+---------------+-------------------+
29+
| JSON | Python |
30+
+===============+===================+
31+
| object | OrderedDict |
32+
+---------------+-------------------+
33+
| array | list |
34+
+---------------+-------------------+
35+
| string | String |
36+
+---------------+-------------------+
37+
| number (int) | int, long |
38+
+---------------+-------------------+
39+
| number (real) | float |
40+
+---------------+-------------------+
41+
| true | True |
42+
+---------------+-------------------+
43+
| false | False |
44+
+---------------+-------------------+
45+
| null | None |
46+
+---------------+-------------------+
47+
48+
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
49+
their corresponding ``float`` values, which is outside the JSON spec.
50+
"""
51+
def __init__(self, parse_float=None, parse_int=None, parse_constant=None,
52+
strict=True):
53+
"""`parse_float``, if specified, will be called with the string
54+
of every JSON float to be decoded. By default this is equivalent to
55+
float(num_str). This can be used to use another datatype or parser
56+
for JSON floats (e.g. decimal.Decimal).
57+
58+
``parse_int``, if specified, will be called with the string
59+
of every JSON int to be decoded. By default this is equivalent to
60+
int(num_str). This can be used to use another datatype or parser
61+
for JSON integers (e.g. float).
62+
63+
``parse_constant``, if specified, will be called with one of the
64+
following strings: -Infinity, Infinity, NaN.
65+
This can be used to raise an exception if invalid JSON numbers
66+
are encountered.
67+
68+
If ``strict`` is false (true is the default), then control
69+
characters will be allowed inside strings. Control characters in
70+
this context are those with character codes in the 0-31 range,
71+
including ``'\\t'`` (tab), ``'\\n'``, ``'\\r'`` and ``'\\0'``.
72+
73+
"""
74+
AbstractJSONDecoderOrderedDict.__init__(self, parse_float=parse_float, parse_int=parse_int, parse_constant=parse_constant, strict=strict);

Diff for: py2/json.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# -*- coding: utf-8 -*-
2+
# This file is part of the pymfony package.
3+
#
4+
# (c) Alexandre Quercia <[email protected]>
5+
#
6+
# For the full copyright and license information, please view the LICENSE
7+
# file that was distributed with this source code.
8+
9+
from __future__ import absolute_import;
10+
11+
from json import decoder;
12+
from collections import OrderedDict;
13+
14+
"""
15+
"""
16+
17+
__all__ = [
18+
'AbstractJSONDecoderOrderedDict',
19+
];
20+
21+
class AbstractJSONDecoderOrderedDict(decoder.JSONDecoder):
22+
def __init__(self, parse_float=None, parse_int=None, parse_constant=None,
23+
strict=True):
24+
decoder.JSONDecoder.__init__(self, object_hook=None, parse_float=parse_float, parse_int=parse_int, parse_constant=parse_constant, strict=strict, object_pairs_hook=self.__object_pairs_hook);
25+
26+
def __object_pairs_hook(self, seq):
27+
return OrderedDict(seq);

Diff for: py26/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# -*- coding: utf-8 -*-
2+
# This file is part of the pymfony package.
3+
#
4+
# (c) Alexandre Quercia <[email protected]>
5+
#
6+
# For the full copyright and license information, please view the LICENSE
7+
# file that was distributed with this source code.
8+
9+
"""
10+
"""

Diff for: py26/json.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# -*- coding: utf-8 -*-
2+
# This file is part of the pymfony package.
3+
#
4+
# (c) Alexandre Quercia <[email protected]>
5+
#
6+
# For the full copyright and license information, please view the LICENSE
7+
# file that was distributed with this source code.
8+
9+
from __future__ import absolute_import;
10+
11+
from json import decoder;
12+
from json import scanner;
13+
14+
from pymfony.component.system.py26.types import OrderedDict;
15+
16+
"""
17+
"""
18+
19+
__all__ = [
20+
'AbstractJSONDecoderOrderedDict',
21+
];
22+
23+
def JSONObject(match, context, _w=decoder.WHITESPACE.match):
24+
pairs = OrderedDict(); # Change to an ordered dict
25+
s = match.string
26+
end = _w(s, match.end()).end()
27+
nextchar = s[end:end + 1]
28+
# Trivial empty object
29+
if nextchar == '}':
30+
return pairs, end + 1
31+
if nextchar != '"':
32+
raise ValueError(decoder.errmsg("Expecting property name", s, end))
33+
end += 1
34+
encoding = getattr(context, 'encoding', None)
35+
strict = getattr(context, 'strict', True)
36+
iterscan = JSONScanner.iterscan
37+
while True:
38+
key, end = decoder.scanstring(s, end, encoding, strict)
39+
end = _w(s, end).end()
40+
if s[end:end + 1] != ':':
41+
raise ValueError(decoder.errmsg("Expecting : delimiter", s, end))
42+
end = _w(s, end + 1).end()
43+
try:
44+
value, end = iterscan(s, idx=end, context=context).next()
45+
except StopIteration:
46+
raise ValueError(decoder.errmsg("Expecting object", s, end))
47+
pairs[key] = value
48+
end = _w(s, end).end()
49+
nextchar = s[end:end + 1]
50+
end += 1
51+
if nextchar == '}':
52+
break
53+
if nextchar != ',':
54+
raise ValueError(decoder.errmsg("Expecting , delimiter", s, end - 1))
55+
end = _w(s, end).end()
56+
nextchar = s[end:end + 1]
57+
end += 1
58+
if nextchar != '"':
59+
raise ValueError(decoder.errmsg("Expecting property name", s, end - 1))
60+
object_hook = getattr(context, 'object_hook', None)
61+
if object_hook is not None:
62+
pairs = object_hook(pairs)
63+
return pairs, end
64+
scanner.pattern(r'{')(JSONObject);
65+
66+
ANYTHING = list(decoder.ANYTHING);
67+
ANYTHING[0] = JSONObject;
68+
JSONScanner = scanner.Scanner(ANYTHING);
69+
70+
class AbstractJSONDecoderOrderedDict(decoder.JSONDecoder):
71+
_scanner = JSONScanner;

Diff for: py26/types.py

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2009 Raymond Hettinger
3+
#
4+
# Permission is hereby granted, free of charge, to any person
5+
# obtaining a copy of this software and associated documentation files
6+
# (the "Software"), to deal in the Software without restriction,
7+
# including without limitation the rights to use, copy, modify, merge,
8+
# publish, distribute, sublicense, and/or sell copies of the Software,
9+
# and to permit persons to whom the Software is furnished to do so,
10+
# subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be
13+
# included in all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
# OTHER DEALINGS IN THE SOFTWARE.
23+
24+
from UserDict import DictMixin
25+
26+
class OrderedDict(dict, DictMixin):
27+
28+
def __init__(self, *args, **kwds):
29+
if len(args) > 1:
30+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
31+
try:
32+
self.__end
33+
except AttributeError:
34+
self.clear()
35+
self.update(*args, **kwds)
36+
37+
def clear(self):
38+
self.__end = end = []
39+
end += [None, end, end] # sentinel node for doubly linked list
40+
self.__map = {} # key --> [key, prev, next]
41+
dict.clear(self)
42+
43+
def __setitem__(self, key, value):
44+
if key not in self:
45+
end = self.__end
46+
curr = end[1]
47+
curr[2] = end[1] = self.__map[key] = [key, curr, end]
48+
dict.__setitem__(self, key, value)
49+
50+
def __delitem__(self, key):
51+
dict.__delitem__(self, key)
52+
key, prev, end = self.__map.pop(key)
53+
prev[2] = end
54+
end[1] = prev
55+
56+
def __iter__(self):
57+
end = self.__end
58+
curr = end[2]
59+
while curr is not end:
60+
yield curr[0]
61+
curr = curr[2]
62+
63+
def __reversed__(self):
64+
end = self.__end
65+
curr = end[1]
66+
while curr is not end:
67+
yield curr[0]
68+
curr = curr[1]
69+
70+
def popitem(self, last=True):
71+
if not self:
72+
raise KeyError('dictionary is empty')
73+
if last:
74+
key = reversed(self).next()
75+
else:
76+
key = iter(self).next()
77+
value = self.pop(key)
78+
return key, value
79+
80+
def __reduce__(self):
81+
items = [[k, self[k]] for k in self]
82+
tmp = self.__map, self.__end
83+
del self.__map, self.__end
84+
inst_dict = vars(self).copy()
85+
self.__map, self.__end = tmp
86+
if inst_dict:
87+
return (self.__class__, (items,), inst_dict)
88+
return self.__class__, (items,)
89+
90+
def keys(self):
91+
return list(self)
92+
93+
setdefault = DictMixin.setdefault
94+
update = DictMixin.update
95+
pop = DictMixin.pop
96+
values = DictMixin.values
97+
items = DictMixin.items
98+
iterkeys = DictMixin.iterkeys
99+
itervalues = DictMixin.itervalues
100+
iteritems = DictMixin.iteritems
101+
102+
def __repr__(self):
103+
if not self:
104+
return '%s()' % (self.__class__.__name__,)
105+
return '%s(%r)' % (self.__class__.__name__, self.items())
106+
107+
def copy(self):
108+
return self.__class__(self)
109+
110+
@classmethod
111+
def fromkeys(cls, iterable, value=None):
112+
d = cls()
113+
for key in iterable:
114+
d[key] = value
115+
return d
116+
117+
def __eq__(self, other):
118+
if isinstance(other, OrderedDict):
119+
if len(self) != len(other):
120+
return False
121+
for p, q in zip(self.items(), other.items()):
122+
if p != q:
123+
return False
124+
return True
125+
return dict.__eq__(self, other)
126+
127+
def __ne__(self, other):
128+
return not self == other

Diff for: py3/json.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# -*- coding: utf-8 -*-
2+
# This file is part of the pymfony package.
3+
#
4+
# (c) Alexandre Quercia <[email protected]>
5+
#
6+
# For the full copyright and license information, please view the LICENSE
7+
# file that was distributed with this source code.
8+
9+
from __future__ import absolute_import;
10+
11+
from json import decoder;
12+
from collections import OrderedDict;
13+
14+
"""
15+
"""
16+
17+
__all__ = [
18+
'AbstractJSONDecoderOrderedDict',
19+
];
20+
21+
class AbstractJSONDecoderOrderedDict(decoder.JSONDecoder):
22+
def __init__(self, parse_float=None, parse_int=None, parse_constant=None,
23+
strict=True):
24+
decoder.JSONDecoder.__init__(self, object_hook=None, parse_float=parse_float, parse_int=parse_int, parse_constant=parse_constant, strict=strict, object_pairs_hook=self.__object_pairs_hook);
25+
26+
def __object_pairs_hook(self, seq):
27+
return OrderedDict(seq);

Diff for: types.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from __future__ import absolute_import;
1010

1111
import re;
12+
import sys;
1213

1314
from pymfony.component.system.oop import abstract;
1415
from pymfony.component.system import Object;
@@ -20,10 +21,10 @@
2021
"""
2122
"""
2223

23-
try:
24+
if sys.version_info > (2, 6):
2425
from collections import OrderedDict;
25-
except ImportError: # < Python 2.7
26-
from pymfony.component.system.py2.types import OrderedDict;
26+
else:
27+
from pymfony.component.system.py26.types import OrderedDict;
2728

2829
class String(AbstractString):
2930
"""Base string to provide back compatibility"""

0 commit comments

Comments
 (0)