Python 解码 JSON 中的嵌套 JSON

n8h*_*rie 5 python recursion json

我正在处理一个 API,不幸的是它返回格式错误(或“奇怪的格式”,而是——感谢@fjarri)JSON,但从积极的一面来看,我认为这可能是我学习一些有关递归以及JSON。这是我用来记录锻炼的应用程序,我正在尝试制作一个备份脚本。

我可以很好地接收 JSON,但即使在requests.get(api_url).json()(或json.loads(requests.get(api_url).text)) 之后,其中一个值仍然是 JSON 编码的字符串。幸运的是,我只需json.loads()字符串即可正确解码为字典。特定的键是可预测的:timezone_id,而其值会有所不同(因为数据已记录在多个时区)。例如,解码,可能是:dumped to file as "timezone_id": {\"name\":\"America/Denver\",\"seconds\":\"-21600\"}",或loaded into Python as'timezone_id': '{"name":"America/Denver","seconds":"-21600"}'

问题是我使用这个 API 来检索相当数量的数据,其中有多层字典和列表,并且双编码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))
Run Code Online (Sandbox Code Playgroud)

有什么好的方法建议json.loads()对于在不更改对象其余部分的情况下处理所有这些双编码值的提前谢谢了!

这篇文章似乎是一个很好的参考:修改深度嵌套结构

编辑:这被标记为这个问题的可能重复- 我认为它相当不同,因为我已经证明 usingjson.loads()不起作用。该解决方案最终需要一个object_hook,我在解码 json 时从未使用过它,并且在上一个问题中没有得到解决。

Mat*_*son 6

因此,object_hook每次 json 加载器完成构建字典时,都会调用 json 加载器中的 。也就是说,它首先调用的是最里面的字典,向外工作。

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)
Run Code Online (Sandbox Code Playgroud)

当我测试它时,它似乎具有我认为你正在寻找的效果。

我可能不会尝试解码每个字符串值——我会策略性地在您期望存在 json 对象双重编码的地方调用它。如果您尝试解码每个字符串,则可能会意外解码一些本应是字符串的内容(例如,"12345"当该字符串本应是 API 返回的字符串时)。

此外,您现有的函数比它需要的更复杂,如果您总是返回obj(无论您是否更新其内容),则可能会按原样工作。