我正在处理一个API,不幸的是,它返回格式错误(或“奇怪的形成”,而不是 - 感谢@fjarri)JSON,但从积极的一面来看,我认为这可能是我学习递归的机会以及JSON。这是我用来记录我的训练的应用程序,我正在尝试制作一个备份脚本。
我可以收到JSON罚款,但即使在requests.get(api_url).json()
(或json.loads(requests.get(api_url).text)
)之后,其中一个值仍然是JSON编码的字符串。幸运的是,我只能json.loads()
字符串,它正确地解码为字典。特定密钥是可预测的:timezone_id
,而其值不同(因为数据已记录在多个时区中)。例如,在解码后的,它可能是:dump
编辑为"timezone_id": {\"name\":\"America/Denver\",\"seconds\":\"-21600\"}"
,或load
编辑为Python 'timezone_id': '{"name":"America/Denver","seconds":"-21600"}'
问题在于我正在使用此API来检索大量数据,其中包含多层dicts和列表,而双重编码timezone_id
则出现在多个级别。
到目前为止,这是我的工作,有一些示例数据,但似乎我离基地很远。
#! /usr/bin/env python3
import json
from pprint import pprint
my_input = r"""{
"hasMore": false,
"checkins": [
{
"timestamp": 1353193745000,
"timezone_id": "{\"name\":\"America/Denver\",\"seconds\":\"-21600\"}",
"privacy_groups": [
"private"
],
"meta": {
"client_version": "3.0",
"uuid": "fake_UUID"
},
"client_id": "fake_client_id",
"workout_name": "Workout (Nov 17, 2012)",
"fitness_workout_json": {
"exercise_logs": [
{
"timestamp": 1353195716000,
"type": "exercise_log",
"timezone_id": "{\"name\":\"America/Denver\",\"seconds\":\"-21600\"}",
"workout_log_uuid": "fake_UUID"
},
{
"timestamp": 1353195340000,
"type": "exercise_log",
"timezone_id": "{\"name\":\"America/Denver\",\"seconds\":\"-21600\"}",
"workout_log_uuid": "fake_UUID"
}
]
},
"workout_uuid": ""
},
{
"timestamp": 1354485615000,
"user_id": "fake_ID",
"timezone_id": "{\"name\":\"America/Denver\",\"seconds\":\"-21600\"}",
"privacy_groups": [
"private"
],
"meta": {
"uuid": "fake_UUID"
},
"created": 1372023457376,
"workout_name": "Workout (Dec 02, 2012)",
"fitness_workout_json": {
"exercise_logs": [
{
"timestamp": 1354485615000,
"timezone_id": "{\"name\":\"America/Denver\",\"seconds\":\"-21600\"}",
"workout_log_uuid": "fake_UUID"
},
{
"timestamp": 1354485584000,
"timezone_id": "{\"name\":\"America/Denver\",\"seconds\":\"-21600\"}",
"workout_log_uuid": "fake_UUID"
}
]
},
"workout_uuid": ""
}]}"""
def recurse(obj):
if isinstance(obj, list):
for item in obj:
return recurse(item)
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, str):
try:
v = json.loads(v)
except ValueError:
pass
obj.update({k: v})
elif isinstance(v, (dict, list)):
return recurse(v)
pprint(json.loads(my_input, object_hook=recurse))
有关json.loads()
所有这些双重编码值的好方法的建议,而不更改对象的其余部分?非常感谢提前!
这篇文章似乎是一个很好的参考:Modifying Deeply-Nested Structures
编辑:这被标记为可能重复的of this question - 我认为它相当不同,因为我已经证明使用json.loads()
无效。该解决方案最终需要object_hook
,我在解码json时从未使用过这个问题,并且在前一个问题中没有解决。
答案 0 :(得分:4)
因此,每次json加载器构建完字典时,都会调用json加载器中的object_hook
。也就是说,它首先被调用的是最里面的字典,向外工作。
给出object_hook
回调的词典是替换该函数返回的内容。
所以,你不需要自己报复。装载程序使您可以首先访问最内层的东西。
我认为这对你有用:
def hook(obj):
value = obj.get("timezone_id")
# this is python 3 specific; I would check isinstance against
# basestring in python 2
if value and isinstance(value, str):
obj["timezone_id"] = json.loads(value, object_hook=hook)
return obj
data = json.loads(my_input, object_hook=hook)
当我测试时,它似乎具有我认为你正在寻找的效果。
我可能不会尝试解码每个字符串值 - 我会策略性地将它调用到您希望存在json对象双重编码的位置。如果你尝试解码每个字符串,你可能会意外地解码一些应该是字符串的东西(比如字符串"12345"
,当它打算成为API返回的字符串时)。
此外,您现有的功能比需要的更复杂,如果您总是返回obj
(无论您是否更新其内容),都可以按原样运行。
答案 1 :(得分:0)
您的主要问题是您的object_hook
函数不应该递归。 json.loads()
负责递归本身并在每次找到字典时调用您的函数(aka obj
将始终是字典)。所以你只想修改有问题的键并返回dict - 这应该是你想要的:
def flatten_hook(obj):
for key, value in obj.iteritems():
if isinstance(value, basestring):
try:
obj[key] = json.loads(value, object_hook=flatten_hook)
except ValueError:
pass
return obj
pprint(json.loads(my_input, object_hook=flatten_hook))
但是,如果您知道有问题的(双重编码的)条目总是采用特定的形式(例如key == 'timezone_id'
),那么仅仅在这些键上调用json.loads()
可能更安全,就像Matt Anderson一样在他的回答中建议。