0

So I have a nested dictionary in Python:

{'entry': {'definition': 'str',
       'endTime': 'str',
       'entryID': {'identifierType': 'str', 'identifierValue': 'str'},
       'instrument': {'FIB': {'FIBSpotSize': {'notes': 'str',
                                              'qualifier': 'str',
                                              'uncertainty': {'uncertaintyType': 'str',
                                                              'value': 'float'},
                                              'unit': 'str',
                                              'value': 'float'},
                              'angleToEBeam': {'notes': 'str',
                                               'qualifier': 'str',
                                               'uncertainty': {'uncertaintyType': 'str',
                                                               'value': 'float'},
                                               'unit': 'str',
                                               'value': 'float'},...
.
.
.

I want a way to simply access a specific key-value pair, given an arbitrarily long keys.

For instance, I want entry.instrument.angleToEBeam.uncertainty.value because I want to update the value of value. I want to write a function that takes a list of keys of arbitrary length, where the last key is the value I'd like to update, and the value which I'd like to set/update. I'm a bit confused on how to navigate/walk these nested dictionaries.

As an example of what I'm trying to do, I have a dict of values to update which looks like

{'entry.instrument.angleToEBeam.uncertainty.value': 2.23', ...}

And I'd like to feed this list into some function such that it updates the nested dict to have values instead of the expected type, here shown just for the uncertainty parameter:

{'entry': {'definition': 'str',
       'endTime': 'str',
       'entryID': {'identifierType': 'str', 'identifierValue': 'str'},
       'instrument': {'FIB': {'FIBSpotSize': {'notes': 'str',
                                              'qualifier': 'str',
                                              'uncertainty': {'uncertaintyType': 'str',
                                                              'value': 'float'},
                                              'unit': 'str',
                                              'value': 'float'},
                              'angleToEBeam': {'notes': 'str',
                                               'qualifier': 'str',
                                               'uncertainty': {'uncertaintyType': 'pct',
                                                               'value': 3.102},
                                               'unit': 'deg',
                                               'value': 2.23},...
.
.
.

Ultimately, these have to be exported as JSON files, hence the nested dictionary. Any ideas? Some related threads:

But I couldn't figure out how to apply them to my situation, as they deal with flattening the dict entirely.

mathwiz97
  • 67
  • 2
  • 11

1 Answers1

1

JMESPath is a powerful solution for querying a nested dict, but unfortunately doesn't seem to offer lvalue options (modification).

In the end, here is something built from first principles:

def xupdate(dct, path, value, createkeys=False):
    if path:
        k, *path = path
        subdct = dct.get(k, {}) if createkeys else dct[k]
        return {**dct, **{k: xupdate(subdct, path, value)}}
    return value

def dotupdate(dct, dotmods, createkeys=False):
    for kdot, value in dotmods.items():
        dct = xupdate(dct, kdot.split('.'), value, createkeys)
    return dct

Example

dct = {
    'entry': {
        'definition': 'str', 'endTime': 'str', 'entryID': {
            'identifierType': 'str', 'identifierValue': 'str'},
        'instrument': {
            'FIB': {
                'FIBSpotSize': {
                    'notes': 'str', 'qualifier': 'str', 'uncertainty': {
                        'uncertaintyType': 'str', 'value': 'float'},
                    'unit': 'str', 'value': 'float'},
                'angleToEBeam': {
                    'notes': 'str', 'qualifier': 'str', 'uncertainty': {
                        'uncertaintyType': 'str', 'value': 'float'},
                    'unit': 'str', 'value': 'float'}}}}}

dotmods = {
    'entry.instrument.FIB.angleToEBeam.uncertainty.value': 2.23,
    'entry.endTime': '2023-01-01',
}

>>> dotupdate(dct, dotmods)
{'entry': {'definition': 'str',
  'endTime': '2023-01-01',
  'entryID': {'identifierType': 'str', 'identifierValue': 'str'},
  'instrument': {'FIB': {'FIBSpotSize': {'notes': 'str',
     'qualifier': 'str',
     'uncertainty': {'uncertaintyType': 'str', 'value': 'float'},
     'unit': 'str',
     'value': 'float'},
    'angleToEBeam': {'notes': 'str',
     'qualifier': 'str',
     'uncertainty': {'uncertaintyType': 'str', 'value': 2.23},
     'unit': 'str',
     'value': 'float'}}}}}

Notes

  1. createkeys: if True, keys that don't exist in the original dict (e.g. typos, incomplete path) are created. In the original question, there was a reference to the non-existent 'entry.instrument.angleToEBeam.uncertainty.value' (missing .FIB); that would create the missing keys, which is probably not desired.
  2. The order of the nested dict stays the same.
  3. The original dict itself is not modified; the returned value is a semi-copy (a deep copy for the parts that are modified; unmodified sub-dicts are copied by reference).
Pierre D
  • 24,012
  • 7
  • 60
  • 96