Python - How to assign/map non-sequential JSON fields onto a dict

Viewed 100

I have a JSON with a dict of keys which are not always present, at least not all of them all the time at the same position. For example, "producers" is not always on array dict [2] present or "directors" not always on [1] at the JSON, it fully depends on the JSON I pass into my function. Depending on what is available at ['plist']['dict']['key'] the content is mapped to dict 0,1,2,3 (except of studio) ...

How can I find the corresponding array for cast, directors, producers etc. as each of them is not always located at the same array number?! In the end I always want to be able to pull out the right data for the right field even if ['plist']['dict']['key'] may vary sometimes according to the mapped dict.

...
def get_plist_meta(element):
    if isinstance(element, dict):
        return element["string"]
    return ", ".join(i["string"] for i in element)

...
### Default map if all fields are present
# 0 = cast
# 1 = directors
# 2 = producers
# 3 = screenwriters
plist_metadata = json.loads(dump_json)
### make fields match the given sequence 0 = cast, 1 = directors etc. ()
if 'cast' in plist_metadata['plist']['dict']['key']:
    print("Cast: ", get_plist_meta(plist_metadata['plist']['dict']['array'][0]['dict']))
if 'directors' in plist_metadata['plist']['dict']['key']:
    print("Directors: ", get_plist_meta(plist_metadata['plist']['dict']['array'][1]['dict']))
if 'producers' in plist_metadata['plist']['dict']['key']:
    print("Producers: ", get_plist_meta(plist_metadata['plist']['dict']['array'][2]['dict']))
if 'screenwriters' in plist_metadata['plist']['dict']['key']:
    print("Screenwriters: ", get_plist_meta(plist_metadata['plist']['dict']['array'][3]['dict']))
if 'studio' in plist_metadata['plist']['dict']['key']:
    print("Studio: ", plist_metadata['plist']['dict']['string'])

JSON:

{
   "plist":{
      "@version":"1.0",
      "dict":{
         "key":[
            "cast",
            "directors",
            "screenwriters",
            "studio"
         ],
         "array":[
            {
               "dict":[
                  {
                     "key":"name",
                     "string":"Martina Piro"
                  },
                  {
                     "key":"name",
                     "string":"Ralf Stark"
                  }
               ]
            },
            {
               "dict":{
                  "key":"name",
                  "string":"Franco Camilio"
               }
            },
            {
               "dict":{
                  "key":"name",
                  "string":"Kai Meisner"
               }
            }
         ],
         "string":"Helix Films"
      }
   }
}

JSON can also be obtained here: https://pastebin.com/JCXRs3Rw

Thanks in advance

2 Answers

If you prefer a more pythonic solution, try this:

# We will use this function to extract the names from the subdicts. We put single items in a new array so the result is consistent, no matter how many names there were.
def get_names(name_dict):
    arrayfied = name_dict if isinstance(name_dict, list) else [name_dict]
    return [o["string"] for o in arrayfied]

# Make a list of tuples
dict = plist_metadata['plist']['dict']
zipped = zip(dict["key"], dict["array"])

# Get the names from the subdicts and put it into a new dict
result = {k: get_names(v["dict"]) for k, v in zipped}

This will give you a new dict that looks like this

{'cast': ['Martina Piro', 'Ralf Stark'], 'directors': ['Franco Camilio'], 'screenwriters': ['Kai Meisner']}

The new dict will only have the keys present in the original dict.

I'd advise to check out things like zip, map and so on as well as list comprehensions and dict comprehensions.

I think this solves your problem:

import json
dump_json = """{"plist":{"@version":"1.0","dict":{"key":["cast","directors","screenwriters","studio"],"array":[{"dict":[{"key":"name","string":"Martina Piro"},{"key":"name","string":"Ralf Stark"}]},{"dict":{"key":"name","string":"Franco Camilio"}},{"dict":{"key":"name","string":"Kai Meisner"}}],"string":"Helix Films"}}}"""
plist_metadata = json.loads(dump_json)

roles = ['cast', 'directors', 'producers', 'screenwriters']                             # all roles
names = {'cast': [], 'directors': [], 'producers': [], 'screenwriters': []}             # stores the final output

j = 0                                                                                   # keeps count of which array entry we are looking at in plist_metadata['plist']['dict']['array']
for x in names.keys():                                                                  # cycle through all the possible roles
    if x in plist_metadata['plist']['dict']['key']:                                     # if a role exists in the keys, we'll store it in names[role_name]
        y = plist_metadata['plist']['dict']['array'][j]['dict']                         # keep track of value
        if isinstance(plist_metadata['plist']['dict']['array'][j]['dict'], dict):       # if its a dict, encase it in a list
            y = [plist_metadata['plist']['dict']['array'][j]['dict']]
        j += 1                                                                          # add to our plist-dict-array index
        names[x] = list(map(lambda x: x['string'], y))                                  # map each of the entries from {"key":"name","string":"Martina Piro"} to just "Martina Piro"
print(names)

def list_names(role_name):
    if role_name not in names.keys():
        return f'Invalid list request: Role name "{role_name}" not found.'
    return f'{role_name.capitalize()}: {", ".join(names[role_name])}'
    
print(list_names('cast'))
print(list_names('audience'))

Output:

{'cast': ['Martina Piro', 'Ralf Stark'], 'directors': ['Franco Camilio'], 'producers': [], 'screenwriters': ['Kai Meisner']}
Cast: Martina Piro, Ralf Stark
Invalid list request: Role name "audience" not found.
Related