Replace numbers in JSON paths (representing positions in arrays) with special objects. This ensures that objects
    in arrays with different `id` values have different JSON paths – and makes it easy to identify such arrays.

def process_flattened(flattened):
    """
    Replace numbers in JSON paths (representing positions in arrays) with special objects. This ensures that objects
    in arrays with different `id` values have different JSON paths – and makes it easy to identify such arrays.
    """
    # Keep arrays in order.
    processed = OrderedDict()

    # Cache identifiers, to avoid minting a new ID for each field of the same object.
    identifiers = {}

    for key in flattened:
        new_key = []
        for end, part in enumerate(key, 1):
            # If this is a path to an item in an array.
            if isinstance(part, int):
                if key[:end] in identifiers:
                    part = identifiers[key[:end]]
                else:
                    # If it is an array of objects, get the `id` value to apply the identifier merge strategy.
                    # http://standard.open-contracting.org/latest/en/schema/merging/#identifier-merge
                    id_value = flattened.get(key[:end] + ('id',))

                    # If the object contained no top-level `id` value, set a unique value.
                    if id_value is None:
                        identifier = uuid.uuid4()
                    else:
                        identifier = id_value

                    # Save the original value. (If the value is an integer, this avoids coercing it to a string.)
                    part = IdValue(identifier)
                    part.original_value = id_value

                    identifiers[key[:end]] = part
            new_key.append(part)
        processed[tuple(new_key)] = flattened[key]

    return processed