Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Room TypeConverter for map

How would you write TypeConverter for Map? My approach was to do it by Moshi

class Converters() {

    val moshi = Moshi
            .Builder()
            .add(KotlinJsonAdapterFactory())
            .build()

    val mapOfStringsType = Types.newParameterizedType(Map::class.java, String::class.java, String::class.java)
    val mapOfStringsAdapter = moshi.adapter<Map<String, String>>(mapOfStringsType)


    @TypeConverter
    fun stringToMap(data: String): Map<String, String> {
        return mapOfStringsAdapter.fromJson(data).orEmpty()
    }

    @TypeConverter
    fun mapToString(map: Map<String, String>): String {
        return mapOfStringsAdapter.toJson(map)
    }
}

However, it smells, because I can't inject Moshi by Converters() constructor. And, I'm afraid, it's not the best performance either.

like image 954
qbait Avatar asked Aug 18 '18 21:08

qbait


Video Answer


2 Answers

I usually use gson for Room TypeConverters. It allows for simple implementation that will work for all object types:

public class StringMapConverter {
    @TypeConverter
    public static Map<String, String> fromString(String value) {
        Type mapType = new TypeToken<Map<String, String>>() {
        }.getType();
        return new Gson().fromJson(value, mapType);
    }

    @TypeConverter
    public static String fromStringMap(Map<String, String> map) {
        Gson gson = new Gson();
        return gson.toJson(map);
    }
}
like image 78
Brian Acker Avatar answered Nov 03 '22 20:11

Brian Acker


I propose a solution for primitive Map values. Such as String, Boolean, Integer, Long etc. See more here https://kotlinlang.org/docs/basic-types.html.

Creating a Gson instance every time you want to get an item from the Room database can be expensive, it's rather a lazy way of solving this issue in such cases.

If you've ever seen a solution for converting a list of strings, the following example should be self-explanatory.

Instead of treating Map as a Kotlin object, treat it as two arrays: keys, and values. Our big helper is TreeMap, since Map.keys returns unsorted Set.

@TypeConverter
fun fromStringMap(value: Map<String, String>): String {
    val sortedMap = TreeMap(value)
    return sortedMap.keys.joinToString(separator = ",").plus("<divider>")
        .plus(sortedMap.values.joinToString(separator = ","))
}

@TypeConverter
fun toStringMap(value: String): Map<String, String> {
    return value.split("<divider>").run {
        val keys = getOrNull(0)?.split(",")?.map { it }
        val values = getOrNull(1)?.split(",")?.map { it }

        val res = hashMapOf<String, String>()
        keys?.forEachIndexed { index, s ->
            res[s] = values?.getOrNull(index) ?: ""
        }
        res
    }
}

Feel free to replace the separator "" with whatever is best for your use case, so you don't run into value/key containing your separator.

like image 22
Jakub Kostka Avatar answered Nov 03 '22 19:11

Jakub Kostka