Kareadita/Kavita · error · KavitaException

errors.import-fields.non-unique-age-ratings

errors.import-fields.non-unique-age-ratings

Error message

errors.import-fields.non-unique-age-ratings

What it means

Thrown by SettingsService.ImportFieldMappings when the AgeRatingMappings dictionary has duplicate keys - detected because the count of distinct keys differs from the total entry count. Since AgeRatingMappings is a dictionary keyed by rating, duplicates indicate the import payload is malformed (the same rating mapped twice with conflicting values).

Source

Thrown at Kavita.Services/SettingsService.cs:131

                    DestinationValue = mappingDto.DestinationValue,
                    ExcludeFromSource = mappingDto.ExcludeFromSource
                });
            }
        }

        // Save changes
        await unitOfWork.CommitAsync(ct);

        // Return updated settings
        return await unitOfWork.SettingsRepository.GetMetadataSettingDto(ct);
    }

    public async Task<FieldMappingsImportResultDto> ImportFieldMappings(FieldMappingsDto dto,
        ImportSettingsDto settings, CancellationToken ct = default)
    {
        if (dto.AgeRatingMappings.Keys.Distinct().Count() != dto.AgeRatingMappings.Count)
        {
            throw new KavitaException("errors.import-fields.non-unique-age-ratings");
        }

        if (dto.FieldMappings.DistinctBy(f => f.Id).Count() != dto.FieldMappings.Count)
        {
            throw new KavitaException("errors.import-fields.non-unique-fields");
        }

        return settings.ImportMode switch
        {
            ImportMode.Merge => await MergeFieldMappings(dto, settings),
            ImportMode.Replace => await ReplaceFieldMappings(dto, settings),
            _ => throw new ArgumentOutOfRangeException(nameof(settings), $"Invalid import mode {nameof(settings.ImportMode)}")
        };
    }

    /// <summary>
    /// Will fully replace any enabled fields, always successful
    /// </summary>

View on GitHub (pinned to 9c3e540000)

Solutions

  1. De-duplicate AgeRatingMappings by key before submitting (keep the last value per key).
  2. Regenerate the export from a current Kavita instance so keys are unique.
  3. Validate the import JSON with a schema that forbids duplicate age-rating keys.
  4. If merging two exports, union keys client-side first.

Example fix

// before
await settingsService.ImportFieldMappings(dto, settings, ct);

// after - dedupe before import
dto.AgeRatingMappings = dto.AgeRatingMappings
    .GroupBy(kvp => kvp.Key)
    .ToDictionary(g => g.Key, g => g.Last().Value);
await settingsService.ImportFieldMappings(dto, settings, ct);
Defensive patterns

Strategy: validation

Validate before calling

if (dto.AgeRatingMappings.Keys.Distinct().Count() != dto.AgeRatingMappings.Count)
{
    dto.AgeRatingMappings = dto.AgeRatingMappings
        .GroupBy(kvp => kvp.Key).ToDictionary(g => g.Key, g => g.Last().Value);
}
await settingsService.ImportFieldMappings(dto, settings, ct);

Type guard

static bool AgeRatingKeysUnique(Dictionary<string,string> m) => m.Keys.Distinct().Count() == m.Count;

Prevention

When it happens

Trigger: Posting a FieldMappingsDto where AgeRatingMappings.Keys.Distinct().Count() != AgeRatingMappings.Count - i.e. the serialized mapping contains the same age-rating key more than once, which JSON deserialization into a Dictionary can produce from certain malformed imports.

Common situations: Hand-edited or older export JSON that repeats a rating key; a migration script merged mappings without deduping; or a UI builder emitted the same rating twice.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/17169d2fbf761958. Report an issue: GitHub.