def flatten_json(unflat_json, flatten_lists=True):
    """Flatten a JSON document into dot notation, where nested dicts are represented with a period.

    Arguments:
        unflat_json (dict): The JSON to flatten.
        flatten_lists (bool): Should the lists be flattened? **Default:** ``True``.
                Lists are flattened by merging contained dictionaries,
                and flattening those. Terminal values (non-container types)
                are added to a list and set at the terminal value for the path.
                When this is ``False``, lists are treated as terminal values and not flattened.

    Returns:
        dict: The JSON, flattened into dot notation in a dictionary.
                If a non-container value was supplied to flatten (e.g. a string)
                the value will be returned unchanged instead.

    Warning:
        Mixing container and non-container types in a list is not recommended.
        (e.g. [{"key": "val"}, "other_val"])
        If a list mixes types in this way, the non-container values MAY be listed
        under the field "flatten_undefined".

    Examples::

        {
            "key1": {
                "key2": "value"
            }
        }
        turns into
        {
            "key1.key2": value
        }


        {
            "key1": {
                "key2": [{
                    "key3": "foo",
                    "key4": "bar"
                }, {
                    "key3": "baz"
                }]
            }
        }
        with flatten_lists=True, turns into
        {
            "key1.key2.key3": ["foo", "baz"],
            "key1.key2.key4": "bar"
        }
    """
    flat_json = {}
    # Dict flattens by keys
    if isinstance(unflat_json, dict):
        for key, val in unflat_json.items():
            flat_val = flatten_json(val, flatten_lists=flatten_lists)
            # flat_val is dict to add to flat_json
            if isinstance(flat_val, dict):
                for subkey, subval in flat_val.items():
                    if subkey != "flatten_undefined":
                        flat_json[key+"."+subkey] = subval
                    # "flatten_unknown" is from mixed-type lists (container and non-container)
                    # Attempt to fix. This is not guaranteed; recommend not mixing types
                    else:
                        flat_json[key] = subval
            # flat_val is a terminal value (anything besides dict)
            else:
                flat_json[key] = flat_val

    # List flattens by values inside
    elif flatten_lists and isinstance(unflat_json, list):
        # Dict of flat keys processed so far
        partial_flats = {}
        # List of terminal values
        terminals = []
        for val in unflat_json:
            flat_val = flatten_json(val, flatten_lists=flatten_lists)
            # flat_val is dict, need to appropriately merge
            if isinstance(flat_val, dict):
                for subkey, subval in flat_val.items():
                    # If subkey is duplicate, add values to list
                    if subkey in partial_flats.keys():
                        # Create list if not already
                        if type(partial_flats[subkey]) is not list:
                            partial_flats[subkey] = [partial_flats[subkey], subval]
                        else:
                            partial_flats[subkey].append(subval)
                    # If subkey not duplicate, just add
                    else:
                        partial_flats[subkey] = subval
            # flat_val is a terminal value (anything besides dict)
            # Lists should be merged into terminals
            elif isinstance(flat_val, list):
                terminals.extend(flat_val)
            # Non-containers just appended to terminals
            else:
                terminals.append(flat_val)

        # Clean up for returning
        # If only one of partial_flats and terminals is populated, return that,
        # but if neither are flattened return an empty dict (partial_flats)
        # partial_flats is all contained dicts, flattened
        if not terminals:
            flat_json = partial_flats
        # terminals is all contained terminal values (flat by definition)
        elif terminals and not partial_flats:
            # If only one value in terminals, just return it
            if len(terminals) == 1:
                terminals = terminals[0]
            flat_json = terminals
        # Otherwise, add in sentinel field "flatten_undefined"
        # This case only occurs when a non-container type is mixed with a container type
        # in a list (e.g. [{"key": "val"}, "other_val"]) and is removed at an earlier
        # recursion depth if possible
        else:
            if len(terminals) == 1:
                terminals = terminals[0]
            partial_flats["flatten_undefined"] = terminals
            flat_json = partial_flats

    # Not container; cannot flatten
    else:
        flat_json = unflat_json
    return flat_json