private function describe($array = [], $keys = '', $sort = true, $payload = false)
    {
        foreach ($array as $key => &$value) {
            if (is_array($value)) {
                if (0 === count($value)) {
                    // Currently such groups are undocumented, so labels are not needed.
                    unset($array[$key]);
                    continue;
                } else {
                    $value = $this->describe($value, $keys.' '.$key, $sort, ($payload || 'payload' === $key));
                }
            } else {
                if ($payload) {
                    // $value = implode('.', explode(' ', trim($keys))).'.'.$key;
                    // Use the last key for easy finding as these get long.
                    $value = $key;
                } elseif (is_bool($value) || null === $value || 0 === $value) {
                    // Discern the "label" given the key and previous keys conjoined.
                    $totalKey = str_replace('_', ' ', $keys.' '.trim($key));
                    preg_match_all('/(?:|[A-Z])[a-z]*/', $totalKey, $words);
                    foreach ($words[0] as &$word) {
                        if (strlen($word) > 1) {
                            // Change the case of the first letter without dropping the case of the rest of the word.
                            $word = strtoupper(substr($word, 0, 1)).substr($word, 1);
                        }
                    }
                    // Combine down to one string without extra whitespace.
                    $value = trim(preg_replace('/\s+/', ' ', implode(' ', $words[0])));
                    // One exception is UTM variables.
                    $value = str_replace('Utm ', 'UTM ', $value);
                }
            }
        }

        if ($sort) {
            ksort($array, SORT_NATURAL);
        }

        return $array;
    }