Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit 9577517

Browse files
authored
Add files via upload
1 parent 798136a commit 9577517

File tree

4 files changed

+365
-1
lines changed

4 files changed

+365
-1
lines changed

LICENSE.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
MIT License
2+
Copyright (c) 2018 YOUR NAME
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
The above copyright notice and this permission notice shall be included in all
10+
copies or substantial portions of the Software.
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17+
SOFTWARE.

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,36 @@
1-
# json_duplicate_keys
1+
# JSON Duplicate Keys
2+
Flatten / Unflatten and Loads / Dumps JSON object with Duplicate Keys
3+
4+
## Installation
5+
```console
6+
pip install json-duplicate-keys
7+
```
8+
9+
## Usage
10+
```python
11+
>>> import json_duplicate_keys
12+
>>>
13+
>>> Jstr = '{"author": "truocphan", "version": "22.3.3", "version": "latest", "release": [{"version": "22.3.3", "version": "latest"}], "snapshot": {"author": "truocphan", "version": "22.3.3", "release": [{"version": "latest"}]}}'
14+
>>> Jobj = json_duplicate_keys.loads(Jstr)
15+
>>> Jobj
16+
{'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': 'latest', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': 'latest'}]}}
17+
>>>
18+
>>> Jobj["version{{{_2_}}}"] = "22.3.14"
19+
>>> Jobj
20+
{'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': 'latest'}]}}
21+
>>> json_duplicate_keys.dumps(Jobj)
22+
'{"author": "truocphan", "version": "22.3.3", "version": "22.3.14", "release": [{"version": "22.3.3", "version": "latest"}], "snapshot": {"author": "truocphan", "version": "22.3.3", "release": [{"version": "latest"}]}}'
23+
>>>
24+
>>> Jobj
25+
{'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': 'latest'}]}}
26+
>>> Jflat = json_duplicate_keys.flatten(Jobj)
27+
>>> Jflat
28+
{'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release||$0$||version': '22.3.3', 'release||$0$||version{{{_2_}}}': 'latest', 'snapshot||author': 'truocphan', 'snapshot||version': '22.3.3', 'snapshot||release||$0$||version': 'latest'}
29+
>>>
30+
>>> Jflat['snapshot||release||$0$||version'] = "22.3.14"
31+
>>> Jflat
32+
{'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release||$0$||version': '22.3.3', 'release||$0$||version{{{_2_}}}': 'latest', 'snapshot||author': 'truocphan', 'snapshot||version': '22.3.3', 'snapshot||release||$0$||version': '22.3.14'}
33+
>>>
34+
>>> json_duplicate_keys.unflatten(Jflat)
35+
{'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': '22.3.14'}]}}
36+
```

json_duplicate_keys/__init__.py

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
import json
2+
3+
# # # # # # # # # # # # # # # # # # # # # # #
4+
# # # # # # # # # # Loads # # # # # # # # # #
5+
# # # # # # # # # # # # # # # # # # # # # # #
6+
"""
7+
>>> INPUT: '{"author": "truocphan", "version": "22.3.3", "version": "latest", "release": [{"version": "22.3.3", "version": "latest"}], "snapshot": {"author": "truocphan", "version": "22.3.3", "release": [{"version": "latest"}]}}'
8+
9+
<<< OUTPUT: {'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': 'latest', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': 'latest'}]}}
10+
"""
11+
def loads(Jstr, dupSign_start="{", dupSign_end="}", _isDebug_=False):
12+
import re
13+
from collections import OrderedDict
14+
import traceback
15+
16+
def __convert_Jloads_to_Jobj(Jloads, Jobj):
17+
if type(Jloads) == OrderedDict:
18+
for k in Jloads.keys():
19+
_key = re.split(dupSign_start_escape_regex*3+"_\d+_"+dupSign_end_escape_regex*3+"$", k)[0]
20+
21+
if _key not in Jobj.keys():
22+
if type(Jloads[k]) not in [list, OrderedDict]:
23+
Jobj[_key] = Jloads[k]
24+
else:
25+
if type(Jloads[k]) == list:
26+
Jobj[_key] = list()
27+
else:
28+
Jobj[_key] = OrderedDict()
29+
30+
__convert_Jloads_to_Jobj(Jloads[k], Jobj[_key])
31+
else:
32+
countObj = len([i for i in Jobj.keys() if _key==re.split(dupSign_start_escape_regex*3+"_\d+_"+dupSign_end_escape_regex*3+"$", i)[0]])
33+
if type(Jloads[k]) not in [list, OrderedDict]:
34+
Jobj[_key+dupSign_start*3+"_"+str(countObj+1)+"_"+dupSign_end*3] = Jloads[k]
35+
else:
36+
if type(Jloads[k]) == list:
37+
Jobj[_key+dupSign_start*3+"_"+str(countObj+1)+"_"+dupSign_end*3] = list()
38+
else:
39+
Jobj[_key+dupSign_start*3+"_"+str(countObj+1)+"_"+dupSign_end*3] = OrderedDict()
40+
41+
__convert_Jloads_to_Jobj(Jloads[k], Jobj[_key+dupSign_start*3+"_"+str(countObj+1)+"_"+dupSign_end*3])
42+
elif type(Jloads) == list:
43+
for i in range(len(Jloads)):
44+
if type(Jloads[i]) not in [list, OrderedDict]:
45+
Jobj.append(Jloads[i])
46+
else:
47+
if type(Jloads[i]) == list:
48+
Jobj.append(list())
49+
else:
50+
Jobj.append(OrderedDict())
51+
52+
__convert_Jloads_to_Jobj(Jloads[i], Jobj[i])
53+
54+
try:
55+
Jloads = json.loads(Jstr, object_pairs_hook=OrderedDict)
56+
57+
if type(Jloads) in [list, OrderedDict] and len(Jloads) > 0:
58+
try:
59+
if type(dupSign_start) not in [str, unicode] or len(dupSign_start) == 0: dupSign_start = "{"
60+
except Exception as e:
61+
if type(dupSign_start) != str or len(dupSign_start) == 0: dupSign_start = "{"
62+
63+
dupSign_start_escape = "".join(["\\\\u"+hex(ord(c))[2:].zfill(4) for c in dupSign_start])
64+
65+
dupSign_start_escape_regex = re.escape(dupSign_start)
66+
67+
68+
try:
69+
if type(dupSign_end) not in [str, unicode] or len(dupSign_end) == 0: dupSign_end = "}"
70+
except Exception as e:
71+
if type(dupSign_end) != str or len(dupSign_end) == 0: dupSign_end = "}"
72+
73+
dupSign_end_escape = "".join(["\\\\u"+hex(ord(c))[2:].zfill(4) for c in dupSign_end])
74+
75+
dupSign_end_escape_regex = re.escape(dupSign_end)
76+
77+
78+
Jstr = re.sub(r'\\\\', '\x00\x01', Jstr)
79+
Jstr = re.sub(r'\\"', '\x02\x03', Jstr)
80+
Jstr = re.sub(r'"([^"]*)"[\s\t\r\n]*([,\]}])', '\x04\x05\\1\x04\x05\\2', Jstr)
81+
82+
83+
Jstr = re.sub(r'"([^"]+)"[\s\t\r\n]*:', r'"\1{dupSign_start}_dupSign_{dupSign_end}":'.format(dupSign_start=dupSign_start_escape*3, dupSign_end=dupSign_end_escape*3), Jstr)
84+
85+
Jstr = re.sub(r'""[\s\t\r\n]*:', '"{dupSign_start}_dupSign_{dupSign_end}":'.format(dupSign_start=dupSign_start_escape*3, dupSign_end=dupSign_end_escape*3), Jstr)
86+
87+
i = 0
88+
while re.search(r'{dupSign_start}_dupSign_{dupSign_end}"[\s\t\r\n]*:'.format(dupSign_start=dupSign_start_escape*3, dupSign_end=dupSign_end_escape*3), Jstr):
89+
Jstr = re.sub(r'{dupSign_start}_dupSign_{dupSign_end}"[\s\t\r\n]*:'.format(dupSign_start=dupSign_start_escape*3, dupSign_end=dupSign_end_escape*3), dupSign_start_escape*3+"_"+str(i)+"_"+dupSign_end_escape*3+'":', Jstr, 1)
90+
i += 1
91+
92+
Jstr = re.sub('\x00\x01', r'\\\\', Jstr)
93+
Jstr = re.sub('\x02\x03', r'\\"', Jstr)
94+
Jstr = re.sub('\x04\x05', r'"', Jstr)
95+
96+
Jloads = json.loads(Jstr, object_pairs_hook=OrderedDict)
97+
98+
if type(Jloads) == list:
99+
Jobj = list()
100+
else:
101+
Jobj = OrderedDict()
102+
103+
__convert_Jloads_to_Jobj(Jloads, Jobj)
104+
105+
return Jobj
106+
else:
107+
return Jloads
108+
except:
109+
if _isDebug_: traceback.print_exc()
110+
return None
111+
# # # # # # # # # # # # # # # # # # # # # # #
112+
# # # # # # # # # # # # # # # # # # # # # # #
113+
# # # # # # # # # # # # # # # # # # # # # # #
114+
115+
116+
117+
118+
119+
# # # # # # # # # # # # # # # # # # # # # # #
120+
# # # # # # # # # # Dumps # # # # # # # # # #
121+
# # # # # # # # # # # # # # # # # # # # # # #
122+
"""
123+
>>> INPUT: {'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': 'latest'}]}}
124+
125+
<<< OUTPUT: '{"author": "truocphan", "version": "22.3.3", "version": "22.3.14", "release": [{"version": "22.3.3", "version": "latest"}], "snapshot": {"author": "truocphan", "version": "22.3.3", "release": [{"version": "latest"}]}}'
126+
"""
127+
def dumps(Jobj, dupSign_start="{", dupSign_end="}", _isDebug_=False, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False):
128+
import re
129+
from collections import OrderedDict
130+
import traceback
131+
132+
try:
133+
if type(Jobj) in [list, dict, tuple, OrderedDict] and len(Jobj) > 0:
134+
try:
135+
if type(dupSign_start) not in [str, unicode]: dupSign_start = "{"
136+
except Exception as e:
137+
if type(dupSign_start) != str: dupSign_start = "{"
138+
139+
dupSign_start_escape_regex = re.escape(json.dumps({dupSign_start:""})[2:-6])
140+
141+
142+
try:
143+
if type(dupSign_end) not in [str, unicode]: dupSign_end = "}"
144+
except Exception as e:
145+
if type(dupSign_end) != str: dupSign_end = "}"
146+
147+
dupSign_end_escape_regex = re.escape(json.dumps({dupSign_end:""})[2:-6])
148+
149+
150+
if len(dupSign_start) == 0 and len(dupSign_end) == 0:
151+
return json.dumps(Jobj, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, cls=cls, indent=indent, separators=separators, default=default, sort_keys=sort_keys)
152+
else:
153+
return re.sub(r'{dupSign_start}_\d+_{dupSign_end}":'.format(dupSign_start=dupSign_start_escape_regex*3, dupSign_end=dupSign_end_escape_regex*3), '":', json.dumps(Jobj, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, cls=cls, indent=indent, separators=separators, default=default, sort_keys=sort_keys))
154+
else:
155+
return json.dumps(Jobj, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, cls=cls, indent=indent, separators=separators, default=default, sort_keys=sort_keys)
156+
except:
157+
if _isDebug_: traceback.print_exc()
158+
return None
159+
# # # # # # # # # # # # # # # # # # # # # # #
160+
# # # # # # # # # # # # # # # # # # # # # # #
161+
# # # # # # # # # # # # # # # # # # # # # # #
162+
163+
164+
165+
166+
167+
# # # # # # # # # # # # # # # # # # # # # # #
168+
# # # # # # # # # Flatten # # # # # # # # # #
169+
# # # # # # # # # # # # # # # # # # # # # # #
170+
"""
171+
>>> INPUT: {'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': 'latest'}]}}
172+
173+
<<< OUTPUT: {'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release||$0$||version': '22.3.3', 'release||$0$||version{{{_2_}}}': 'latest', 'snapshot||author': 'truocphan', 'snapshot||version': '22.3.3', 'snapshot||release||$0$||version': 'latest'}
174+
"""
175+
def flatten(Jobj, separator="||", parse_index="$", _isDebug_=False):
176+
from collections import OrderedDict
177+
import traceback
178+
179+
Jflat = OrderedDict()
180+
181+
def __convert_Jobj_to_Jflat(Jobj, key=None):
182+
if type(Jobj) in [dict, OrderedDict]:
183+
if len(Jobj) == 0:
184+
Jflat[key] = OrderedDict()
185+
else:
186+
for k,v in Jobj.items():
187+
_Jobj = v
188+
_key = "{key}{separator}{k}".format(key=key,separator=separator,k=k) if key != None else "{k}".format(k=k)
189+
190+
__convert_Jobj_to_Jflat(_Jobj, _key)
191+
elif type(Jobj) == list:
192+
if len(Jobj) == 0:
193+
Jflat[key] = list()
194+
else:
195+
for i,v in enumerate(Jobj):
196+
_Jobj = v
197+
_key = "{key}{separator}{parse_index}{i}{parse_index}".format(key=key, separator=separator, parse_index=parse_index, i=i) if key != None else "{parse_index}{i}{parse_index}".format(parse_index=parse_index, i=i)
198+
199+
__convert_Jobj_to_Jflat(_Jobj, _key)
200+
else:
201+
Jflat[key] = Jobj
202+
203+
try:
204+
if type(Jobj) in [list, dict, OrderedDict]:
205+
if len(Jobj) == 0:
206+
if type(Jobj) == list:
207+
if _isDebug_: print("+ [DEBUG::flatten] Unable to Flatten an empty List")
208+
return None
209+
else:
210+
return Jflat
211+
else:
212+
try:
213+
if type(separator) not in [str, unicode] or len(separator) == 0: separator = "||"
214+
except Exception as e:
215+
if type(separator) != str or len(separator) == 0: separator = "||"
216+
217+
try:
218+
if type(parse_index) not in [str, unicode] or len(parse_index) == 0: parse_index = "$"
219+
except Exception as e:
220+
if type(parse_index) != str or len(parse_index) == 0: parse_index = "$"
221+
222+
__convert_Jobj_to_Jflat(Jobj)
223+
224+
return Jflat
225+
else:
226+
if _isDebug_: print("+ [DEBUG::flatten] Unable to Flatten the {datatype} Object: \"{value}\"".format(datatype=type(Jobj), value=Jobj))
227+
return None
228+
except:
229+
if _isDebug_: traceback.print_exc()
230+
return None
231+
# # # # # # # # # # # # # # # # # # # # # # #
232+
# # # # # # # # # # # # # # # # # # # # # # #
233+
# # # # # # # # # # # # # # # # # # # # # # #
234+
235+
236+
237+
238+
239+
# # # # # # # # # # # # # # # # # # # # # # #
240+
# # # # # # # # # Unflatten # # # # # # # # #
241+
# # # # # # # # # # # # # # # # # # # # # # #
242+
"""
243+
>>> INPUT: {'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release||$0$||version': '22.3.3', 'release||$0$||version{{{_2_}}}': 'latest', 'snapshot||author': 'truocphan', 'snapshot||version': '22.3.3', 'snapshot||release||$0$||version': '22.3.14'}
244+
245+
<<< OUTPUT: {'author': 'truocphan', 'version': '22.3.3', 'version{{{_2_}}}': '22.3.14', 'release': [{'version': '22.3.3', 'version{{{_2_}}}': 'latest'}], 'snapshot': {'author': 'truocphan', 'version': '22.3.3', 'release': [{'version': '22.3.14'}]}}
246+
"""
247+
def unflatten(Jflat, separator="||", parse_index="$", _isDebug_=False):
248+
import re
249+
from collections import OrderedDict
250+
import traceback
251+
252+
try:
253+
if type(Jflat) in [dict, OrderedDict]:
254+
if len(Jflat) == 0:
255+
return OrderedDict()
256+
else:
257+
Jobj = list() if len([k for k in Jflat.keys() if re.compile("^"+re.escape(parse_index)+"\d+"+re.escape(parse_index)+"$").match(str(k).split(separator)[0])]) == len(Jflat.keys()) else OrderedDict()
258+
259+
for k, v in Jflat.items():
260+
Jtmp = Jobj
261+
Jkeys = k.split(separator)
262+
263+
for count, (Jkey, next_Jkeys) in enumerate(zip(Jkeys, Jkeys[1:] + [v]), 1):
264+
v = next_Jkeys if count == len(Jkeys) else list() if re.compile("^"+re.escape(parse_index)+"\d+"+re.escape(parse_index)+"$").match(next_Jkeys) else OrderedDict()
265+
266+
if type(Jtmp) == list:
267+
Jkey = int(re.compile(re.escape(parse_index)+"(\d+)"+re.escape(parse_index)).match(Jkey).group(1))
268+
269+
while Jkey >= len(Jtmp):
270+
Jtmp.append(v)
271+
272+
elif Jkey not in Jtmp:
273+
Jtmp[Jkey] = v
274+
275+
Jtmp = Jtmp[Jkey]
276+
277+
return Jobj
278+
else:
279+
if _isDebug_: print("+ [DEBUG::unflatten] Unable to Unflatten the {datatype} Object: \"{value}\"".format(datatype=type(Jflat), value=Jflat))
280+
return None
281+
except:
282+
if _isDebug_: traceback.print_exc()
283+
return None
284+
# # # # # # # # # # # # # # # # # # # # # # #
285+
# # # # # # # # # # # # # # # # # # # # # # #
286+
# # # # # # # # # # # # # # # # # # # # # # #

setup.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# python setup.py bdist_wheel sdist
2+
# twine upload --skip-existing dist/*
3+
import setuptools
4+
5+
with open("README.md", "r") as fh:
6+
long_description = fh.read()
7+
8+
setuptools.setup(
9+
name="json_duplicate_keys",
10+
version="22.4.28",
11+
author="Truoc Phan",
12+
license="MIT",
13+
author_email="truocphan112017@gmail.com",
14+
description="Flatten / Unflatten and Loads / Dumps JSON object with Duplicate Keys",
15+
long_description=long_description,
16+
long_description_content_type="text/markdown",
17+
install_requires=[],
18+
url="https://github.com/truocphan/json_duplicate_keys",
19+
classifiers=[
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 2",
22+
"Programming Language :: Python :: Implementation :: Jython"
23+
],
24+
keywords=["json", "duplicate keys", "json duplicate keys", "flatten", "unflatten"],
25+
packages=["json_duplicate_keys"],
26+
)

0 commit comments

Comments
 (0)