title: Python decode nested JSON in JSON
body: <p>I'm dealing with an API that unfortunately is returning malformed (or "weirdly formed," rather -- thanks @fjarri) JSON, but on the positive side I think it may be an opportunity for me to learn something about recursion as well as JSON. It's for an app I use to log my workouts, I'm trying to make a backup script.</p>
  
  <p>I can received the JSON fine, but even after <code>requests.get(api_url).json()</code> (or <code>json.loads(requests.get(api_url).text)</code>), one of the values is still a JSON encoded string. Luckily, I can just <code>json.loads()</code> the string and it properly decodes to a dict. The specific key is predictable: <code>timezone_id</code>, whereas its value varies (because data has been logged in multiple timezones). For example, <em>after</em> decoding, it might be: <code>dump</code>ed to file as <code>"timezone_id": {\"name\":\"America/Denver\",\"seconds\":\"-21600\"}"</code>, or <code>load</code>ed into Python as <code>'timezone_id': '{"name":"America/Denver","seconds":"-21600"}'</code></p>
  
  <p>The problem is that I'm using this API to retrieve a fair amount of data, which has several layers of dicts and lists, and the double encoded <code>timezone_id</code>s occur at multiple levels.</p>
  
  <p>Here's my work so far with some example data, but it seems like I'm pretty far off base.</p>
  
  <pre><code>#! /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))
  </code></pre>
  
  <p>Any suggestions for a good way to <code>json.loads()</code> all those double-encoded values without changing the rest of the object? Many thanks in advance!</p>
  
  <p>This post seems to be a good reference: <a href="http://nvie.com/posts/modifying-deeply-nested-structures/" rel="nofollow noreferrer">Modifying Deeply-Nested Structures</a></p>
  
  <p>Edit: This was flagged as a possible duplicate <a href="https://stackoverflow.com/questions/2331943/how-to-decode-json-with-python">of this question</a> -- I think its fairly different, as I've already demonstrated that using <code>json.loads()</code> was not working. The solution ended up requiring an <code>object_hook</code>, which I've never had to use when decoding json and is not addressed in the prior question.</p>