def joindicts(dest, *dictlist): """Merges one or more dictionaries into dest recursively. Dictionaries are merged, lists are merged. Scalars and strings are ignored. Returns the generated result. To merge with overwrite, use the native dict update method. """ result = dest for inputdict in dictlist: for key, value in inputdict.items(): # If this is a new value, take as is if key not in result: result[key] = value continue # Handle collision existing_value = dest[key] if typecheck.is_dict(existing_value) and typecheck.is_dict(value): result[key] = joindicts(existing_value, value) elif typecheck.is_list(existing_value) and typecheck.is_list( value): result[key] = existing_value + value else: raise Exception("Failed to merge dictionaries: " + f"could not resolve collision on key '{key}'") return result
def flatten_dict(d, prefix='', result=None): "Flattens a dictionary using path separators" # recursive algorithm where each pass gives the result object to the next patch # The key algorithm boils down to: # - If its not a scalar (dict or list), build the prefix # - If its a scalar (string or number), assign the value result = {} if result is None else result if typecheck.is_dict(d): # dictionaries add a / behind them for non-root ones if prefix: prefix = prefix + '/' for key, value in d.items(): if typecheck.is_scalar(value): result[f"{prefix}{key}"] = value else: flatten_dict(value, prefix=prefix + key, result=result) elif typecheck.is_list(d): for idx, value in enumerate(d): if typecheck.is_scalar(value): result[f'{prefix}[{idx}]'] = value else: flatten_dict(value, prefix=f'{prefix}[{idx}]', result=result) else: raise Exception('Unsupported type ' + str(type(d))) return result
def struct_to_json(obj): "Recursively serializes a binary to a dictionary" from mhdata import typecheck if hasattr(obj, 'as_dict'): obj = obj.as_dict() if typecheck.is_dict(obj): return {key: struct_to_json(value) for key, value in obj.items()} elif typecheck.is_flat_iterable(obj): return [struct_to_json(value) for value in obj] return obj
def flatten(obj, *, nest, prefix={}): """Flattens a nested object into a list of flat dictionaries nest is a list of fieldnames. Do not use prefix, its internal. """ # This is a recursive algorithm. # We iteratively step through "nest levels". # BASE CASE if not nest: items = obj if not typecheck.is_flat_iterable(obj): items = [obj] # Error checking, items must be dictionaries if any(not typecheck.is_dict(item) for item in items): raise TypeError("Flattened entries must be a mapping type") # Extend all items with the prefix and return them return [{**prefix, **item} for item in items] # Validation if not isinstance(obj, collections.Mapping): raise ValueError("Object is not sufficiently deep for flattening") # Return multiple results by stepping down one depth level, # and recursively calling it for the sub-items current_nest = nest[0] remaining_nest = nest[1:] results = [] for nest_value, remaining_data in obj.items(): sub_prefix = {**prefix, current_nest: nest_value} # RECURSIVE CALL sub_flattened = flatten(remaining_data, nest=remaining_nest, prefix=sub_prefix) results.extend(sub_flattened) return results