Kotlin Convert Data Class to Map

December 16, 2021

This is not a straightforward solution, as my use case is slightly differ from the common one.

You will need to add kotlin-reflect.

depedencies {
    implementation "org.jetbrains.kotlin:kotlin-reflect"

A lot of the annotation is for Firestore: @IgnoreExtraProperties, @PropertyName, which is not necessary for your use case.

data class Card(
    override var id: String? = null,
    var modified: Timestamp? = null,
    var title: String? = null,
    @get:PropertyName(IMAGE_IDS) @set:PropertyName(IMAGE_IDS)
    var imageIds: List<String>? = null,
    // var place: Place? = null,
) : FirestoreData {

    companion object {
        const val COLLECTION_NAME = "card"

        const val MODIFIED = "modified"
        const val TITLE = "title"
        // const val PLACE = "place"
        const val IMAGE_IDS = "image_ids"

        fun colRef(firestore: FirebaseFirestore, uid: String) = UserPrivate.colRef(firestore).document(uid).collection(

        fun toObject(doc: DocumentSnapshot): Card? {
            val item = doc.toObject<Card>()
            item?.id = doc.id
            return item

    val isNew: Boolean
        get() = id == null

    override fun getData(filterNull: Boolean): Map<String, Any?> {
        return FirestoreDataUtil.toMap(this, excludeNames = listOf(::COLLECTION_NAME.name), filterNull)

An Interfect class to indicate support to convert data class to map.

interface FirestoreData {
    var id: String? // optional
    fun getData(filterNull: Boolean = true) : Map<String, Any?>


  • Find all the constant public string in companion object to find all the field names (ability to exclude certain fields). With this method, I can add new property to this class where I don’t want it to be included in the map.
  • Get the fields and their value based on these field names.
  • If object is instance of FirestoreData (like the commented Place field), call getData to get its underlying field.
class FirestoreDataUtil {
    companion object {
        fun toMap(instance: Any, excludeNames: List<String>, filterNull: Boolean = true) : Map<String, Any?> {
            val findMember = instance::class.declaredMemberProperties
                .filter{ it.visibility == KVisibility.PUBLIC }
                .filterIsInstance<KProperty1<Any, *>>()
                        val annotation = it.getter.findAnnotation<PropertyName>()
                        annotation?.value ?: it.name
                    { it }

            var data = instance::class.companionObject!!.declaredMemberProperties
                .filter { it.visibility == KVisibility.PUBLIC && it.isConst && it.returnType == String::class.createType() && !excludeNames.contains(it.name) }
                .mapNotNull {
                    { it },
                        // raise exception or return null to ignore
                        val prop = findMember[it] ?: throw Exception("$it not found")
                        val value = prop.get(instance)
                        if (value != null) {
                            if (value::class.isData) {
                                value::class.memberFunctions.find {  func -> func.name == "toMap" }?.call(value, filterNull) ?: value

                            // TODO("Check value is List<FirestoreData> or Map<Any, FirestoreData>")
                            if (value is FirestoreData) {
                            else value
                        } else null

            if (filterNull) {
                data = data.filterValues { it != null }
            return data


I didn’t use Json or Kotlin Serialization approach because it require each field to be converted into basic type (e.g. Timestamp to epoch milli seconds). I need the value to maintain it’s original type.

Originally I use Kotlin Reflect to find the member fields of the data class, but I might accidentally add a new property (e.g. isNew property) which I don’t intend for it to end up in the map.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.