tui-cs/Terminal.Gui · error · JsonException

{value}: Invalid Rune.

Error message

{value}: Invalid Rune.

What it means

Thrown by RuneJsonConverter.Read (RuneJsonConverter.cs:46-49) when a string value starts with 'U+'/'\U' (encoded form) but the codepoint-extraction regex matched either zero captures or more than two captures. Zero means the prefix was present but no valid hex group followed; more than two means the string encodes 3+ codepoints, which cannot form a single Rune (max is a surrogate pair or char+combining-mark).

Source

Thrown at Terminal.Gui/Configuration/RuneJsonConverter.cs:48

                    int first = RuneExtensions.MaxUnicodeCodePoint + 1;
                    int second = RuneExtensions.MaxUnicodeCodePoint + 1;

                    if (value is { } && (value.StartsWith ("U+", StringComparison.OrdinalIgnoreCase)
                                         || value.StartsWith ("\\U", StringComparison.OrdinalIgnoreCase)))
                    {
                        // Handle encoded single char, surrogate pair, or combining mark + char
                        uint [] codePoints = Regex.Matches (value, @"(?:\\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})")
                                                  .Select (
                                                           match => uint.Parse (
                                                                                match.Groups [1].Value,
                                                                                NumberStyles.HexNumber
                                                                               )
                                                          )
                                                  .ToArray ();

                        if (codePoints.Length is 0 or > 2)
                        {
                            throw new JsonException ($"{value}: Invalid Rune.");
                        }

                        if (codePoints.Length > 0)
                        {
                            first = (int)codePoints [0];
                        }

                        if (codePoints.Length == 2)
                        {
                            second = (int)codePoints [1];
                        }
                    }
                    else
                    {
                        // Handle single character, surrogate pair, or combining mark + char
                        if (value is { Length: 0 or > 2 })
                        {
                            throw new JsonException ($"{value}: Invalid Rune");

View on GitHub (pinned to 2e47b11478)

Solutions

  1. Provide at most two codepoints in the encoded string: one for a single scalar, or two for a surrogate pair / char+combining-mark combination.
  2. Ensure at least one valid hexadecimal group follows U+/\u (1-8 hex digits).
  3. For a single emoji that is a multi-codepoint grapheme cluster (e.g. family emoji), pick a single-codepoint equivalent or store it differently — a Rune cannot hold a cluster of 3+.
  4. Verify the hex digits are valid (0-9, A-F, a-f).

Example fix

// before (three codepoints)
"Check": "U+1F600+1F601+1F602"

// after (single codepoint)
"Check": "U+1F600"
Defensive patterns

Strategy: validation

Validate before calling

using System.Text.RegularExpressions;
using System.Globalization;

static bool IsValidEncodedRune (string? value)
{
    if (string.IsNullOrEmpty (value)) return false;
    if (!(value!.StartsWith ("U+", StringComparison.OrdinalIgnoreCase)
          || value.StartsWith ("\\U", StringComparison.OrdinalIgnoreCase)))
        return true; // literal form, handled separately

    uint[] codePoints = Regex.Matches (value, @"(?:\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})")
        .Select (m => uint.Parse (m.Groups[1].Value, NumberStyles.HexNumber))
        .ToArray ();
    return codePoints.Length is 1 or 2;
}

if (!IsValidEncodedRune (runeValue))
{
    // supply 1 or 2 valid codepoints
}

Type guard

static bool IsValidEncodedRune (string? value)
{
    if (string.IsNullOrEmpty (value)) return false;
    if (!(value!.StartsWith ("U+", StringComparison.OrdinalIgnoreCase)
          || value.StartsWith ("\\U", StringComparison.OrdinalIgnoreCase)))
        return true;
    uint[] cps = Regex.Matches (value, @"(?:\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})")
        .Select (m => uint.Parse (m.Groups[1].Value, NumberStyles.HexNumber)).ToArray ();
    return cps.Length is 1 or 2;
}

Try / catch

try
{
    ConfigurationManager.Load (configJson);
}
catch (JsonException ex) when (ex.Message.Contains ("Invalid Rune"))
{
    // An encoded Rune had 0 or >2 codepoints. Supply 1-2 valid hex codepoints and reload.
}

Prevention

When it happens

Trigger: A Rune config value is an encoded string like "U+1F600+1F601+1F602" (three codepoints) or "U+ZZZZ" (no valid hex digits, regex yields zero matches), or "\u" alone with no hex. The regex (?:\[uU]\+?|U\+)([0-9A-Fa-f]{1,8}) fails to produce 1 or 2 captures.

Common situations: User concatenates multiple U+ codepoints into one string expecting a grapheme cluster. User typos the hex digits. User copies a multi-codepoint emoji sequence into a single Rune field (Runes are single scalar values or char+combining-mark pairs, not arbitrary grapheme clusters).

Related errors


AI-assisted analysis of tui-cs/Terminal.Gui@2e47b11478 (2026-08-13). Data as JSON: /api/errors/587880deda2a1a8f. Report an issue: GitHub.