##// END OF EJS Templates
don't clobber existing dicts in extract_dates/squash_dates
MinRK -
Show More
@@ -1,132 +1,134 b''
1 1 """Utilities to manipulate JSON objects.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 # stdlib
14 14 import re
15 15 import types
16 16 from datetime import datetime
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Globals and constants
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # timestamp formats
23 23 ISO8601="%Y-%m-%dT%H:%M:%S.%f"
24 24 ISO8601_PAT=re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+$")
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes and functions
28 28 #-----------------------------------------------------------------------------
29 29
30 30 def extract_dates(obj):
31 31 """extract ISO8601 dates from unpacked JSON"""
32 32 if isinstance(obj, dict):
33 obj = dict(obj) # don't clobber
33 34 for k,v in obj.iteritems():
34 35 obj[k] = extract_dates(v)
35 36 elif isinstance(obj, (list, tuple)):
36 37 obj = [ extract_dates(o) for o in obj ]
37 38 elif isinstance(obj, basestring):
38 39 if ISO8601_PAT.match(obj):
39 40 obj = datetime.strptime(obj, ISO8601)
40 41 return obj
41 42
42 43 def squash_dates(obj):
43 44 """squash datetime objects into ISO8601 strings"""
44 45 if isinstance(obj, dict):
46 obj = dict(obj) # don't clobber
45 47 for k,v in obj.iteritems():
46 48 obj[k] = squash_dates(v)
47 49 elif isinstance(obj, (list, tuple)):
48 50 obj = [ squash_dates(o) for o in obj ]
49 51 elif isinstance(obj, datetime):
50 52 obj = obj.strftime(ISO8601)
51 53 return obj
52 54
53 55 def date_default(obj):
54 56 """default function for packing datetime objects in JSON."""
55 57 if isinstance(obj, datetime):
56 58 return obj.strftime(ISO8601)
57 59 else:
58 60 raise TypeError("%r is not JSON serializable"%obj)
59 61
60 62
61 63
62 64 def json_clean(obj):
63 65 """Clean an object to ensure it's safe to encode in JSON.
64 66
65 67 Atomic, immutable objects are returned unmodified. Sets and tuples are
66 68 converted to lists, lists are copied and dicts are also copied.
67 69
68 70 Note: dicts whose keys could cause collisions upon encoding (such as a dict
69 71 with both the number 1 and the string '1' as keys) will cause a ValueError
70 72 to be raised.
71 73
72 74 Parameters
73 75 ----------
74 76 obj : any python object
75 77
76 78 Returns
77 79 -------
78 80 out : object
79 81
80 82 A version of the input which will not cause an encoding error when
81 83 encoded as JSON. Note that this function does not *encode* its inputs,
82 84 it simply sanitizes it so that there will be no encoding errors later.
83 85
84 86 Examples
85 87 --------
86 88 >>> json_clean(4)
87 89 4
88 90 >>> json_clean(range(10))
89 91 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
90 92 >>> json_clean(dict(x=1, y=2))
91 93 {'y': 2, 'x': 1}
92 94 >>> json_clean(dict(x=1, y=2, z=[1,2,3]))
93 95 {'y': 2, 'x': 1, 'z': [1, 2, 3]}
94 96 >>> json_clean(True)
95 97 True
96 98 """
97 99 # types that are 'atomic' and ok in json as-is. bool doesn't need to be
98 100 # listed explicitly because bools pass as int instances
99 101 atomic_ok = (basestring, int, float, types.NoneType)
100 102
101 103 # containers that we need to convert into lists
102 104 container_to_list = (tuple, set, types.GeneratorType)
103 105
104 106 if isinstance(obj, atomic_ok):
105 107 return obj
106 108
107 109 if isinstance(obj, container_to_list) or (
108 110 hasattr(obj, '__iter__') and hasattr(obj, 'next')):
109 111 obj = list(obj)
110 112
111 113 if isinstance(obj, list):
112 114 return [json_clean(x) for x in obj]
113 115
114 116 if isinstance(obj, dict):
115 117 # First, validate that the dict won't lose data in conversion due to
116 118 # key collisions after stringification. This can happen with keys like
117 119 # True and 'true' or 1 and '1', which collide in JSON.
118 120 nkeys = len(obj)
119 121 nkeys_collapsed = len(set(map(str, obj)))
120 122 if nkeys != nkeys_collapsed:
121 123 raise ValueError('dict can not be safely converted to JSON: '
122 124 'key collision would lead to dropped values')
123 125 # If all OK, proceed by making the new dict that will be json-safe
124 126 out = {}
125 127 for k,v in obj.iteritems():
126 128 out[str(k)] = json_clean(v)
127 129 return out
128 130
129 131 # If we get here, we don't know how to handle the object, so we just get
130 132 # its repr and return that. This will catch lambdas, open sockets, class
131 133 # objects, and any other complicated contraption that json can't encode
132 134 return repr(obj)
General Comments 0
You need to be logged in to leave comments. Login now