docs: def flatten(obj, merge_rules=None, path=None, flattened=None):
      """
      Flattens a JSON object into key-value pairs, in which the key is the JSON path as a tuple. For example:
  
      {
          "a": "I am a",
          "b": ["A", "list"],
          "c": [
              {"ca": "I am ca"},
              {"cb": "I am cb"}
          ]
      }
  
      flattens to:
  
      {
          ('a',): 'I am a',
          ('b',): ['A', 'list'],
          ('c', 0, 'ca'): 'I am ca',
          ('c', 1, 'cb'): 'I am cb',
      }
      """
      if merge_rules is None:
          merge_rules = {}
      if path is None:
          path = ()
      if flattened is None:
          flattened = OrderedDict()
  
      if isinstance(obj, dict):
          iterable = obj.items()
          if not iterable:
              flattened[path] = OrderedDict()
      else:
          iterable = enumerate(obj)
          if not iterable:
              flattened[path] = []
  
      for key, value in iterable:
          new_path = path + (key,)
          # Remove array indices to find the merge rule for this JSON path in the data.
          new_path_merge_rules = merge_rules.get(tuple(part for part in new_path if not isinstance(part, int)), [])
  
          if 'omitWhenMerged' in new_path_merge_rules:
              continue
          # If it is neither an object nor an array, if it is `wholeListMerge`, or if it is an array containing
          # non-objects (even if `wholeListMerge` is `false`), use the whole list merge strategy.
          # Note: Behavior is undefined and inconsistent if the array is not in the schema and contains objects in some
          # cases but not in others.
          # See http://standard.open-contracting.org/1.1-dev/en/schema/merging/#whole-list-merge
          # See http://standard.open-contracting.org/1.1-dev/en/schema/merging/#objects
          elif (not isinstance(value, (dict, list)) or 'wholeListMerge' in new_path_merge_rules or
                  isinstance(value, list) and any(not isinstance(item, dict) for item in value)):
              flattened[new_path] = value
          # Recurse into non-empty objects, and arrays of objects that aren't `wholeListMerge`.
          elif value:
              flatten(value, merge_rules, new_path, flattened)
  
      return flattened