Ну вот как у меня сейчас сделано. Ключи находятся на статическом поле двух классов с ключами конфигов. Один - общая логика и ядро программы, второй - предметная т.е. rss и т.п., для приложения существует один конфиг и везде инжектится через сеттер базовый конфиг, а классы с ключами разные. Т.е. если контроллер дерева rss-лент наследуется от компонента ядра, который уже содержит конфиг, то без параметризации в него попадает тот конфиг, на который завязано ядро программы - т.е. базовый класс конфига.
Теперь допустим, я создам класс конфига со всеми полями, инициализирую в руте приложения и передам главному контроллеру, который будет передавать его всем остальным. Разделить конфиг можно сделав общий и отнаследовать от него доменный и тогда выйдет, что в ядро программы мне нужно совать общий - все работает, а вот в предметную логику - предметный конфиг, но это проблематично, ядро завязано на базовый. Есть выход отнаследовать наоборот - базовый от предметного, хотя это и не слишком красиво, но главное работает, я пробовал такое в одном проекте.
ОДнако простой конфиг в общем случае - это структура данных ключ-значение по цепи файл -> ключ-значение -> объект, поэтому такой объект-конфиг с типами полей это следующий уровень апи, более высокий, а значит можно сделать шаг назад на более низкий - прямой работой с ключ-значение. Т.к. это более низкий уровень, он менее безопасен + вероятность рантаймовых ошибок, отсутствующий ключ, усложняет количество поддерживаемых типов значений и т.п, это минус.
Но для мапинга появляется синхронизация уже между двумя структурами ключ-значение, где вторая набор полей объекта, по аналогии с бд мапингом рождаются частные случаи: кол-во полей != кол-во ключей в файле, если в поле null или какая-то ерунда, то это из-за того что в файле конфига null или из-за того, что маппер косячит, файл конфига содержит ключ ключевое слово, например, "class" и имя поля не может быть именем ключа, имя ключа в файле конфига резко поменялось и т.п., грабли добавляются уже со стороны маппера, что для меня в принципе уравновешивает ошибки с работой напрямую. Ну а если dyaml выдает мне ключ-значение, то зачем мне морочиться с мапингом и делать дополнительную работу? Ну и та же классика, точка в ключе конфига "app.title", верхний и нижний регистр и т.п, к полю выдвигаются более строгие требования по именованию + соглашение о наименовании в языке, поэтому прямое отображение подходит лишь для очень простых случаев, а сложное начинает вставлять палки в колеса, особенно если есть, например, два разных типов - нужно два маппера.
Поэтому я выбрал такой способ скорее из-за его универсальности и возможности портирования пакета конфига на другие языки, в любом языке есть ключ-значение, а вот доступ к полям объектов значительно различаются. Близкий пример из последнего: при переводе cli-приложения на Flutter он навернул мне рефлексивный маппер, мол, сорян, рефлексии тут нет, придумывай чо хош.
Прошу прощения за длинный текст)