public static bool Validate(JObject jobject, FileDiagnostics diagnostics, DocRange range)
        {
            SettingValidation validation = new SettingValidation();

            // Check for invalid properties.
            foreach (JProperty setting in jobject.Properties())
            {
                if (!new string[] { "Lobby", "Modes", "Heroes" }.Contains(setting.Name))
                {
                    validation.InvalidSetting(setting.Name);
                }
            }

            // Check lobby settings.
            if (jobject.TryGetValue("Lobby", out JToken lobbySettings))
            {
                ValidateSetting(validation, LobbySettings, lobbySettings);
            }

            // Check modes.
            if (jobject.TryGetValue("Modes", out JToken modes))
            {
                ModeSettingCollection.Validate(validation, (JObject)modes);
            }

            // Check heroes.
            if (jobject.TryGetValue("Heroes", out JToken heroes))
            {
                HeroesRoot.Validate(validation, (JObject)heroes);
            }

            validation.Dump(diagnostics, range);
            return(!validation.HasErrors());
        }
        public static bool Validate(JObject jobject, FileDiagnostics diagnostics, DocRange range)
        {
            SettingValidation validation = new SettingValidation();

            // Check for invalid properties.
            foreach (JProperty setting in jobject.Properties())
            {
                if (!new string[] { "Lobby", "Modes", "Heroes", "Description", "Workshop", "Extensions", "Mode Name" }.Contains(setting.Name))
                {
                    validation.InvalidSetting(setting.Name);
                }
            }

            // Check lobby settings.
            if (jobject.TryGetValue("Lobby", out JToken lobbySettings))
            {
                ValidateSetting(validation, LobbySettings, lobbySettings);
            }

            // Check modes.
            if (jobject.TryGetValue("Modes", out JToken modes))
            {
                ModeSettingCollection.Validate(validation, (JObject)modes);
            }

            // Check heroes.
            if (jobject.TryGetValue("Heroes", out JToken heroes))
            {
                HeroesRoot.Validate(validation, (JObject)heroes);
            }

            // Check description.
            if (jobject.TryGetValue("Description", out JToken description) && description.Type != JTokenType.String)
            {
                validation.IncorrectType("Description", "string");
            }

            // Check mode name.
            if (jobject.TryGetValue("Mode Name", out JToken modeName) && modeName.Type != JTokenType.String)
            {
                validation.IncorrectType("Mode Name", "string");
            }

            // Check extensions.
            if (jobject.TryGetValue("Extensions", out JToken extensionsToken)
                // Make sure the extension group's value is an object.
                && validation.TryGetObject("Extensions", extensionsToken, out var extensions))
            {
                // Check each extension.
                foreach (var prop in extensions)
                {
                    // The extension name does not exist.
                    if (!ExtensionInfo.Extensions.Any(e => e.Name == prop.Key))
                    {
                        validation.Error($"The extension '{prop.Key}' does not exist.");
                    }
                    // The extension value is not a boolean.
                    else if (prop.Value.Type != JTokenType.Boolean)
                    {
                        validation.Error($"The value of the extension '{prop.Key}' must be a boolean.");
                    }
                }
            }

            validation.Dump(diagnostics, range);
            return(!validation.HasErrors());
        }