Stryker
Stryker

Reputation: 6120

Find and modify python nested dictionary (key, value)

I have a json file that I need to update. I am converting it to a python dict (nested) to update it. Here is the input, but it could be any dept. I'm sure there is a better way to do this, but don't know.

Ultimatley I want to be able to perfom Create/Delete action in addition to the update.


Here is the script and input file.

# Now find TARGET value in nested key value chain

# Replace old value with NEWVALUE

import json
from pprint import pprint
d1 = open('jinputstack.json', 'r')
d1 = json.load(d1)

def traverse(obj, path=None, callback=None):
    """
    Traverse Python object structure, calling a callback function for every element in the structure,
    and inserting the return value of the callback as the new value.
    """
    if path is None:
        path = []

    if isinstance(obj, dict):
        value = {k: traverse(v, path + [k], callback)
                 for k, v in obj.items()}
    elif isinstance(obj, list):
        value = [traverse(elem, path + [[]], callback)
                 for elem in obj]
    else:
        value = obj

    if callback is None:
        # print("Starting value Found-----------------------------------------------------")
        print(value)
        return value

    else:
        print(path, value)
        return callback(path, value)


def traverse_modify(obj, target_path, action):
    """
    Traverses any arbitrary object structure and performs the given action on the value, 
    replacing the node with the
    action's return value.
    """
    target_path = to_path(target_path)
    pprint(value)
    pprint(target_path)

    def transformer(path, value):
        if path == target_path:
            print(action)
            d2 = data["groups"][0]["properties"][1]["value"]["data"][2]["object"]["name"].update(action)
            return d2

        else:
            return value

    return traverse(obj, callback=transformer)


def to_path(path):
    """
    Helper function, converting path strings into path lists.
        >>> to_path('foo')
        ['foo']
        >>> to_path('foo.bar')
        ['foo', 'bar']
        >>> to_path('foo.bar[]')
        ['foo', 'bar', []]
    """
    if isinstance(path, list):
        return path  # already in list format

    def _iter_path(path):

        #pprint(path.split)

        for parts in path.split('[]'):
            for part in parts.strip('.').split('.'):
                yield part
            yield []

    return list(_iter_path(path))[:-1]

def updateit(newvalue):

    data["groups"][0]["properties"][1]["value"]["data"][2]["object"]["name"] = newvalue 
    print(data["groups"][0]["properties"][1]["value"]["data"][2]["object"]["name"])
    return data["groups"][0]["properties"][1]["value"]["data"][2]["object"]["name"]

traverse_modify(d1, d1["groups"][0]["properties"][1]["value"]["data"][1]["object"]["name"], updateit("XXXXXXXXXXXXXX"))

json_data = json.dumps(data)

f = open("jinputstack.json","w")
f.write(json_data)
f.close()

jinputstack.json = {
  "groups": [
    {
      "name": "group1",
      "properties": [
        {
          "name": "Test-Key-String",
          "value": {
            "type": "String",
            "encoding": "utf-8",
            "data": "value1"
          }
        },
        {
          "name": "Test-Key-ValueArray",
          "value": {
            "type": "ValueArray",
            "data": [
              {
                "data": true
              },
              {
                "type": "Blob",
                "object": {
                  "name": "John Su",
                  "age": 25,
                  "salary": 104000.45,
                  "married": false,
                  "gender": "Male"
                }
              }
            ]
          }
        }
      ],
      "groups": [
        {
          "name": "group-child",
          "properties": [
            {
              "name": "Test-Key-String"
            },
            {
              "name": "Test-Key-List",
              "value": {
                "type": "List",
                "data": [
                  "String1",
                  "String2",
                  "String3"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "group2",
      "properties": [
        {
          "name": "Test-Key2-String",
          "value": {
            "type": "String",
            "encoding": "utf-8",
            "data": "value2"
          }
        },
        {
          "name": "MicroBox"
        }
      ]
    }
  ]
}

Credit goes to original author: Vincent Driessen

Upvotes: 1

Views: 1486

Answers (1)

chapelo
chapelo

Reputation: 2562

I think the best way would be to convert the Json object to XML and use ElementTree and XPath to parse and modify your object. Later you can revert to Json if you need:

import json
from xmljson import parker
from lxml.etree import Element

dataxml = parker.etree(datajson, root=Element('root'))
print(dataxml.find('.//data//name').text)            # John Su
dataxml.find('.//data//name').text = "Joan d'Arc"
print(dataxml.find('.//data//name').text)            # Joan d'Arc
print(json.dumps(parker.data(dataxml)))

There are some packages that do something like XPath on a Json string directly. One of them, jsonpath-rw changes the syntax. I prefer to stick with the standard XPath syntax.

from jsonpath_rw import jsonpath, parse

expr = parse('$..data..name')   # Notice that . is now $ and / is now .
                                # Confusing enough?
expr.find(datajson)[0] = 'yyyy'
print(expr.find(datajson)[0].value)                  # John Su

Another one xjpath very simple and perhaps easier to learn, does not give you much difference than what you are doing now.

import xjpath

xj = xjpath.XJPath(datajson)
print(xj['[email protected][email protected][email protected]'])

# Not much different than your code:
print(data["groups"][0]["properties"][1]["value"]["data"][1]["object"]["name"])

I hope this helps.

Upvotes: 1

Related Questions