I am trying to loop through every value in a deeply nested/mixed list and convert any Decimal instances to string so that I can store them in mongo.
My attempt at recursion reached the max depth. I would like to solve this iteratively or in a performant manner.
This doesn't seem to work but is my latest attempt:
def convert_decimals(root_obj):
objs_to_convert = [root_obj]
while objs_to_convert:
obj = objs_to_convert.pop(0)
for k, v in enumerate(obj):
if len(v):
objs_to_convert.append(v)
elif isinstance(v, Decimal):
obj[k] = str(v)
Sample Input:
[
{
'Payments': {
'Payment': Decimal('495.64'),
'IsCapped': True,
'OtherFees': Decimal('0'),
'CapCostTotal': Decimal('27900'),
'Name': 'TestData',
'Program': {
'ProgramName': u'AST',
'Description': None
},
'Rate': Decimal('0.0254'),
'APR': Decimal('2.54'),
'AppliedIds': [
],
'Tax': Decimal('0')
}
}
]
After conversion, the decimal instances should be strings
To convert Decimal
during json.dump()
using default
parameter:
import json
import sys
from decimal import Decimal
def default(obj):
if isinstance(obj, Decimal):
return str(obj)
else:
raise TypeError(obj)
# convert during dump
json.dump(data, sys.stdout, indent=2, default=default)
To modify data
inplace:
import json
import sys
from collections import MutableMapping, MutableSequence
from decimal import Decimal
def convert_decimal(json_data):
stack = [json_data]
while stack:
json_data = stack.pop()
if isinstance(json_data, MutableMapping): # json object
it = json_data.items()
elif isinstance(json_data, MutableSequence): # json array
it = enumerate(json_data)
else: # scalar data
continue
for k, v in it:
if isinstance(v, Decimal):
json_data[k] = str(v)
else:
stack.append(v)
# convert inplace
convert_decimal(data)
json.dump(data, sys.stdout, indent=2)
Both scripts produce the same output:
[
{
"Payments": {
"OtherFees": "0",
"APR": "2.54",
"Rate": "0.0254",
"IsCapped": true,
"Name": "TestData",
"Program": {
"Description": null,
"ProgramName": "AST"
},
"AppliedIds": [],
"CapCostTotal": "27900",
"Tax": "0",
"Payment": "495.64"
}
}
]
You could simplify convert_decimal()
if you use a generic get_items()
function:
def convert_decimal(json_data):
stack = [json_data]
while stack:
json_data = stack.pop()
for k, v in get_items(json_data):
if isinstance(v, Decimal):
json_data[k] = str(v)
else:
stack.append(v)
You want to convert decimals to strings, but recursively apply your function to the contents of lists and the values of dictionaries, otherwise return objects unchanged? Then do that:
def strip_decimals(o):
if type(o) == Decimal:
return str(o)
elif type(o) == list:
return map(strip_decimals, o)
elif type(o) == dict:
return dict([(k, strip_decimals(v)) for k, v in o.iteritems()])
else:
return o
Results in:
[{'Payments': {'APR': '2.54',
'AppliedIds': [],
'CapCostTotal': '27900',
'IsCapped': True,
'Name': 'TestData',
'OtherFees': '0',
'Payment': '495.64',
'Program': {'Description': None, 'ProgramName': u'AST'},
'Rate': '0.0254',
'Tax': '0'}}]