func flattenFix(keyvals []interface{}) []interface{} {
	// opinionated constants for names of keys
	const (
		keyMsg           = "msg"
		keyError         = "error"
		keyMissingPrefix = "_p"
	)

	// Indicates whether the keyvals slice needs to be flattened.
	// Start with true if it has an odd number of items.
	requiresFlattening := isOdd(len(keyvals))

	// Used for estimating the size of the flattened slice
	// in an attempt to use one memory allocation.
	var estimatedLen int

	// Do the keyvals include a "msg" key. This is not entirely
	// reliable if "msg" is passed as a value, but it is only used
	// as a heuristic for naming missing keys.
	var haveMsg bool

	for i, val := range keyvals {
		switch v := val.(type) {
		case List:
			requiresFlattening = true
			// TODO(jpj): recursively descending into the keyvals
			// will come up with a reasonably length estimate, but
			// for now just double the number of elements in the slice
			// and this is probably accurate enough.
			estimatedLen += len(v)
		case keyvalser:
			// unknown implementation: calling Keyvals could result in
			// additional memory allocation, so use a conservative guess
			requiresFlattening = true
			estimatedLen += 16
		case string:
			if v == keyMsg {
				// Remember that we already have a "msg" key, which
				// will be used for inferring missing key names later.
				haveMsg = true
			}
		default:
			estimatedLen++
			if isEven(i) {
				// Non-string in an even position could mean a missing
				// key name in the list.
				estimatedLen++
				requiresFlattening = true
			}
		}
	}

	if !requiresFlattening {
		// Nothing to do, so return the input unmodified.
		return keyvals
	}

	// The missingKey function is passed recursively to flattening
	// and fixing functions. After flattening and fixing we know if
	// one or more missing keys have been inserted, and then we know
	// to iterate through and name them in order.
	var hasMissingKeys bool
	missingKey := func() interface{} {
		hasMissingKeys = true
		return missingKeyT("MISSING")
	}

	// In most circumstances this output slice will have the
	// required capacity.
	output := make([]interface{}, 0, estimatedLen)

	// Perform the actual flattening and fixing.
	output = flatten(output, keyvals, missingKey)

	// If there were any missing keys inserted, iterate through the
	// list and name them. Doing this last allows the names to be
	// ordered from left to right.
	if hasMissingKeys {
		counter := 0 // used for counting _p1, _p2, etc
		for i, v := range output {
			if _, ok := v.(missingKeyT); ok {
				// assign a name for the missing key, depends on the type
				// of the value associated with the key
				var keyName string
				switch output[i+1].(type) {
				case string:
					if !haveMsg {
						// If there is no 'msg' key, the first string
						// value gets 'msg' as its key.
						haveMsg = true
						keyName = keyMsg
					}
				case error:
					if haveMsg {
						// If there is already a 'msg' key, then an
						// error gets 'error' as the key.
						keyName = keyError
					} else {
						// If there is no 'msg' key, the first error
						// value gets 'msg' as its key.
						haveMsg = true
						keyName = keyMsg
					}
				}
				if keyName == "" {
					// Otherwise, missing keys all have a prefix that is
					// unlikely to clash with others key names, and are
					// numbered from 1.
					counter++
					keyName = keyMissingPrefix + strconv.Itoa(counter)
				}
				output[i] = keyName
			}
		}
	}

	return output
}
func flattenFix(keyvals []interface{}) []interface{} {
	// opinionated constants for names of keys
	const (
		keyMsg           = "msg"
		keyError         = "error"
		keyMissingPrefix = "_p"
	)

	// Indicates whether the keyvals slice needs to be flattened.
	// Start with true if it has an odd number of items.
	requiresFlattening := isOdd(len(keyvals))

	// Used for estimating the size of the flattened slice
	// in an attempt to use one memory allocation.
	var estimatedLen int

	// Do the keyvals include a "msg" key. This is not entirely
	// reliable if "msg" is passed as a value, but it is only used
	// as a heuristic for naming missing keys.
	var haveMsg bool

	for i, val := range keyvals {
		switch v := val.(type) {
		case List:
			requiresFlattening = true
			// TODO(jpj): recursively descending into the keyvals
			// will come up with a reasonably length estimate, but
			// for now just double the number of elements in the slice
			// and this is probably accurate enough.
			estimatedLen += len(v)
		case keyvalser:
			// unknown implementation: calling Keyvals could result in
			// additional memory allocation, so use a conservative guess
			requiresFlattening = true
			estimatedLen += 16
		case string:
			if v == keyMsg {
				// Remember that we already have a "msg" key, which
				// will be used for inferring missing key names later.
				haveMsg = true
			}
		default:
			estimatedLen++
			if isEven(i) {
				// Non-string in an even position could mean a missing
				// key name in the list.
				estimatedLen++
				requiresFlattening = true
			}
		}
	}

	if !requiresFlattening {
		// Nothing to do, so return the input unmodified.
		return keyvals
	}

	// The missingKey function is passed recursively to flattening
	// and fixing functions. After flattening and fixing we know if
	// one or more missing keys have been inserted, and then we know
	// to iterate through and name them in order.
	var hasMissingKeys bool
	missingKey := func() interface{} {
		hasMissingKeys = true
		return missingKeyT("MISSING")
	}

	// In most circumstances this output slice will have the
	// required capacity.
	output := make([]interface{}, 0, estimatedLen)

	// Perform the actual flattening and fixing.
	output = flatten(output, keyvals, missingKey)

	// If there were any missing keys inserted, iterate through the
	// list and name them. Doing this last allows the names to be
	// ordered from left to right.
	if hasMissingKeys {
		counter := 0 // used for counting _p1, _p2, etc
		for i, v := range output {
			if _, ok := v.(missingKeyT); ok {
				// assign a name for the missing key, depends on the type
				// of the value associated with the key
				var keyName string
				switch output[i+1].(type) {
				case string:
					if !haveMsg {
						// If there is no 'msg' key, the first string
						// value gets 'msg' as its key.
						haveMsg = true
						keyName = keyMsg
					}
				case error:
					if haveMsg {
						// If there is already a 'msg' key, then an
						// error gets 'error' as the key.
						keyName = keyError
					} else {
						// If there is no 'msg' key, the first error
						// value gets 'msg' as its key.
						haveMsg = true
						keyName = keyMsg
					}
				}
				if keyName == "" {
					// Otherwise, missing keys all have a prefix that is
					// unlikely to clash with others key names, and are
					// numbered from 1.
					counter++
					keyName = keyMissingPrefix + strconv.Itoa(counter)
				}
				output[i] = keyName
			}
		}
	}

	return output
}
// flattenFix accepts a keyvals slice and "flattens" it into a slice
// of alternating key/value pairs. See the examples.
//
// flattenFix will also check the result to ensure it is a valid
// slice of key/value pairs. The returned slice is guaranteed
// to have an even number of items, and every item at an even-numbered
// index is guaranteed to be a string.