Kotlin Object Multiplatform Mapper: сопоставляем коллекции правильно
Map’им List’ы в AI генераторе изображений
После публикации первой статьи я получил несколько отзывов и предложений, некоторые из которых взял в разработку. Первой доработкой стал автоматический маппинг коллекций.
Iterrable как источник
Проведя небольшое исследование, я пришел к выводу, что проверять относится ли поле к коллекции лучше всего по наличию интерфейса Iterable в родителях. Это дает следующие возможности:
Возможность вызывать такие методы как: toList (), toSet () и т.д.
Имеется метод map для внутреннего сопоставления.
Сопоставляем коллекции с одинаковыми типами элементов
Тут, в целом, все тоже самое, что и было уже реализовано для сопоставления свойств в целом:
class SourceObject {
val intList = listOf(1, 2, 3)
}
@KOMMMap(from = SourceObject::class)
data class DestinationObject(
val intList: List
@MapFrom("intList")
val intSet: Set
)
public fun SourceObject.toDestinationObject(): DestinationObject = DestinationObject(
intList = intList,
intSet = intList.toSet()
)
Nullable коллекция в не'nullable будет сопоставляться в таком случае по тем же правилам, что и другие свойства. Интересное начинается, когда…
Сопоставляем коллекции с разными типами элементов
class SourceObject {
val intList = listOf(1, 2, 3)
}
@KOMMMap(from = SourceObject::class)
data class DestinationObject(
@MapFrom("intList")
val stringList: List
)
public fun SourceObject.toDestinationObject(): DestinationObject = DestinationObject(
stringList = intList.map { it.toString() }
)
И вроде все легко, но что, если свойство-приёмник и само по себе отличается от исходника:
class SourceObject {
val intList = listOf(1, 2, 3)
}
@KOMMMap(from = SourceObject::class)
data class DestinationObject(
@MapFrom("intList")
val stringList: MutableList
)
public fun SourceObject.toDestinationObject(): DestinationObject = DestinationObject(
stringList = intList.map { it.toString() }.toMutableList()
)
Да тоже ничего сложного! А что если свойство-исходник еще и nullable? При allowNotNullAssertion все вроде легко:
class SourceObject {
val intList: List? = listOf(1, 2, 3)
}
@KOMMMap(from = SourceObject::class, config = MapConfiguration(allowNotNullAssertion = true))
data class DestinationObject(
@MapFrom("intList")
val stringList: MutableList
)
public fun SourceObject.toDestinationObject(): DestinationObject = DestinationObject(
stringList = intList!!.map { it.toString() }.toMutableList()
)
Применим NullSubstitute. И вот тут начинается сложность, ибо надо все покрывать ?.:
class SourceObject {
val intList: List? = listOf(1, 2, 3)
}
@KOMMMap(from = SourceObject::class, config = MapConfiguration(allowNotNullAssertion = true))
data class DestinationObject(
@NullSubstitute(MapDefault(StringListResolver::class), "intList")
val stringList: MutableList
)
public fun SourceObject.toDestinationObject(): DestinationObject = DestinationObject(
stringList = intList?.map { it.toString() }?.toMutableList() ?: StringListResolver(null).resolve()
)
Однако нужно еще учесть нюанс, что map всегда возвращает List, и делать приведение в этом случае не нужно:
class SourceObject {
val intSet: Set? = setOf(1, 2, 3)
}
@KOMMMap(from = SourceObject::class, config = MapConfiguration(allowNotNullAssertion = true))
data class DestinationObject(
@NullSubstitute(MapDefault(StringListResolver::class), "intSet")
val stringList: List
)
public fun SourceObject.toDestinationObject(): DestinationObject = DestinationObject(
stringList = intSet?.map { it.toString() } ?: StringListResolver(null).resolve()
)
Что дальше
Следующим в работу может уйти сопоставления коллекций с другими свойствами. Тут придется повозиться с такими случаями, как String <-> List<*>, ибо нужно тогда указывать делитель. Может быть надо сделать глобальную настройку в KOMMMap, с возможностью перебить в аннотации самого свойства. Однако делать отдельную аннотацию для этого, выглядит не очень. Но и добавлять такую опцию в каждую существующую аннотацию (по примеру с name) тоже не выглядит идеальным решением. Плюс, совершенно не ясно, нужны ли случаи List
Новая версия библиотеки 0.2.0, когда вы читаете эту статью, скорее всего уже доступна.
Буду держать в курсе развития библиотеки!