felixse/FluentTerminal · error · ParseThemeException
Root node was not a dictionary.
Error message
Root node was not a dictionary.
What it means
Thrown by ITermThemeParser.Parse when PList.Load(fileContent) returns a node that is not a DictionaryNode. iTerm .itermcolors files are Apple property-list files whose root must be a dictionary keyed by color names (e.g. "Ansi 0 Color"). If the deserialized root is an array, string, data, or any non-dict PNode, the `as DictionaryNode` cast yields null and the ?? coalesce throws ParseThemeException.
Source
Thrown at FluentTerminal.App.Services/Implementation/ITermThemeParser.cs:67
{
public const string BlueComponent = "Blue Component";
public const string GreenComponent = "Green Component";
public const string RedComponent = "Red Component";
}
public Task<TerminalTheme> Parse(string fileName, Stream fileContent)
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentNullException(nameof(fileName));
}
if (fileContent == null)
{
throw new ArgumentNullException(nameof(fileContent));
}
var node = PList.Load(fileContent) as DictionaryNode ?? throw new ParseThemeException("Root node was not a dictionary.");
return Task.FromResult(new TerminalTheme
{
Name = Path.GetFileNameWithoutExtension(fileName),
Colors = GetColors(node),
Id = Guid.NewGuid(),
PreInstalled = false
});
}
private TerminalColors GetColors(DictionaryNode themeDictionary)
{
return new TerminalColors
{
Background = GetColorString(themeDictionary[ITermThemeKeys.BackgroundColor]),
Foreground = GetColorString(themeDictionary[ITermThemeKeys.ForegroundColor]),
Cursor = GetColorString(themeDictionary[ITermThemeKeys.CursorColor]),
CursorAccent = GetColorString(themeDictionary[ITermThemeKeys.CursorTextColor]),View on GitHub (pinned to ba83ec485e)
Solutions
- Validate the file is a real iTerm theme before parsing: open it in a text editor (XML plists) and confirm the top-level <dict> ... </dict>; re-export from iTerm2 if missing.
- Catch ParseThemeException around the Parse/Import call and show the user a friendly 'invalid theme file' message instead of crashing.
- If authoring themes programmatically, ensure the serialized root is a DictionaryNode (PListNet.Nodes.DictionaryNode) before writing.
Example fix
// before
var node = PList.Load(fileContent) as DictionaryNode ?? throw new ParseThemeException("Root node was not a dictionary.");
// after - surface the actual root type to aid debugging
var loaded = PList.Load(fileContent);
var node = loaded as DictionaryNode;
if (node == null)
throw new ParseThemeException($"Root node was not a dictionary (was {loaded?.GetType().Name ?? "null"})."); Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate the plist stream is a dict before handing off to the parser.
using (var reader = new StreamReader(fileContent)) { /* peek not trivial for binary plist */ }
// Easiest: validate after load via PListNet and reject early:
var loaded = PList.Load(fileContent);
if (!(loaded is PListNet.Nodes.DictionaryNode))
return Task.FromException<TerminalTheme>(
new ParseThemeException("Theme root must be a dictionary.")); Type guard
static bool IsDictionaryRoot(PNode node) => node is PListNet.Nodes.DictionaryNode;
Try / catch
try { theme = await parser.Parse(name, stream); }
catch (ParseThemeException ex) { logger.Warn(ex, "Invalid theme {Name}"); notifyUser("Theme file is not a valid iTerm theme."); } Prevention
- Only feed genuine iTerm2-exported .itermcolors files to ITermThemeParser.
- Wrap Parse/Import in try/ParseThemeException at the UI layer and show a friendly message.
- Validate SupportedFileTypes extension before parsing to reject obvious mismatches.
When it happens
Trigger: Calling Parse(fileName, stream) with a stream whose contents are not a plist dictionary: an empty file, a plist whose root is an <array>, a JSON plist that is a list, a binary plist with a non-dict top object, or a completely different file format passed with a .itermcolors extension.
Common situations: User downloads or hand-edits a .itermcolors theme and saves it as an array or strips the root dict; a theme exported by a different terminal (not iTerm) is renamed to .itermcolors; the file is truncated/corrupted during download so PListNet falls back to a non-dict node.
Related errors
- Color node was not a dictionary
- Red node value was not a real number
- Green node value was not a real number
- UserInfo part contains {parts.Length} elements.
- Invalid link.
AI-assisted analysis of felixse/FluentTerminal@ba83ec485e (2026-08-13).
Data as JSON: /api/errors/fb1be4422fc0970d.
Report an issue: GitHub.