Home > OS >  Change value in nested JSON string
Change value in nested JSON string

Time:07-24

I want to process a nested JSON with an Azure Function and Python. The JSON has more than 3 sometimes up to 10 or more nested layers. this is a simple example of a JSON passed to the function:

[{
  "node1": {
    "tattr": {
      "usqx": "qhi123"
    },
    "x-child": [{
      "el1": "ast",
      "tattr": {
        "usx": "xht",
        "ust": "cr12"
      },
      "x-child": [{
        "el1": "asx",
        "tattr": {
          "usx": "md0"
        },
        "x-child": [{
          "el1": "ast",
          "tattr": {
            "usx": "mdw"
          },
          "x-child": [{
            "el1": "ast",
            "tattr": {
              "usx": "mdh"
            },
            "x-child": [{
              "el1": "ast",
              "x-child": "placeholder_a"
            }]
          }, {
            "el1": "ast",
            "tattr": {
              "usx": "ssq"
            },
            "x-child": "placeholder_c"
          }, {
            "el1": "div",
            "tattr": {
              "usx": "mdf"
            },
            "x-child": "abc"
          }]
        }]
      }]
    }]
  }
}, {
  "node02": {
    "tattr": {
      "usx": "qhi123"
    }
  }
}]

In this example, placeholder_a should be replaced.

Somewhere in this is a value that needs to be replaced. My idea is to recursive iterate the JSON and process every key that has a dict or list as value. I think the recursive call of the function with a part of the JSON string just copies the JSON. So if the searched string will be find and changed in one recursion, it does not change the original string. What is the best approach to get the "placeholder" in the JSON replaced? It can be on every level.

Since my approach seems to be wrong, I am looking for ideas how to solve the issue. Currently I am between a simple string replace, where I am not sure if the replaced string will be a key or value in a JSON or a recursive function that takes the JSON, search and replace and rebuild the JSON on every recusion.

The code finds the search_para and replaces it but it will not be changed in the original string.

def lin(json_para,search_para,replace_para):
    json_decoded = json.loads(json_para)
    if isinstance(json_decoded,list):
        for list_element in json_decoded:
            lin(json.dumps(list_element))
    elif isinstance(json_decoded,dict):
        for dict_element in json_decoded:
            if isinstance(json_decoded[dict_element],dict):
                lin(json.dumps(json_decoded[dict_element]))
            elif isinstance(json_decoded[dict_element],str):
                if str(json_decoded[dict_element]) == 'search_para:
                    json_decoded[dict_element] = replace_para

CodePudding user response:

You can use recursion as follows:

data = [{'k1': 'placeholder_a', 'k2': [{'k3': 'placeholder_b', 'k4': 'placeholder_a'}]}, {'k5': 'placeholder_a', 'k6': 'placeholder_c'}]

def replace(data, val_from, val_to):
    if isinstance(data, list):
        return [replace(x, val_from, val_to) for x in data]
    if isinstance(data, dict):
        return {k: replace(v, val_from, val_to) for k, v in data.items()}
    return val_to if data == val_from else data # other cases

print(replace(data, "placeholder_a", "REPLACED"))
# [{'k1': 'REPLACED', 'k2': [{'k3': 'placeholder_b', 'k4': 'REPLACED'}]}, {'k5': 'REPLACED', 'k6': 'placeholder_c'}]

I've changed the input/output for the sake of simplicity. You can check that the function replaces 'placeholder_a' at any level with 'REPLACED'.

CodePudding user response:

While it certainly could be accomplished via recursion given the nature of the problem, I think there's an even more elegant approach based on an idea I got a long time ago reading @Mike Brennan’s answer to another JSON-related question titled How to get string objects instead of Unicode from JSON?

The basic idea is to use the optional object_hook parameter that both the json.load and json.loads() functions accept to watch what is being decoded and check it for the sought-after value (and replace it when it's encountered). The function passed will be called with the result of any JSON object literal decoded (i.e. a dict) — in other words at any depth. What may not be obvious is that the dict can also be changed if desired.

This nice thing about this overall approach is that it's based (primarily) on prewritten, debugged, and relatively fast code because it's part of the standard library. It also allows the object_hook callback function to be kept relatively simple.

Here's what I'm suggesting:

import json

def replace_placeholders(json_para, search_para, replace_para):

    # Local nested function.
    def decode_dict(a_dict):
        if search_para in a_dict.values():
            for key, value in a_dict.items():
                if value == search_para:
                    a_dict[key] = replace_para
        return a_dict

    return json.loads(json_para, object_hook=decode_dict)


result = replace_placeholders(json_para, 'placeholder_a', 'REPLACEMENT')
print(json.dumps(result, indent=2))
  • Related