jakub
jakub

Reputation: 3844

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.

Upvotes: 8

Views: 5860

Answers (6)

Mbuodile Obiosio
Mbuodile Obiosio

Reputation: 1543

I use Moshi too and here's a plain to understand solution

class TypeConverter {

    @TypeConverter
    fun fromMapType(currency: String): Map<String, String>? {
        val type = Types.newParameterizedType(
            MutableMap::class.java,
            String::class.java,
            String::class.java
        )
        return Moshi.Builder().build().adapter<Map<String, String>>(type).fromJson(currency)
    }

    @TypeConverter
    fun fromString(map: Map<String, String>): String {
        val type = Types.newParameterizedType(
            MutableMap::class.java,
            String::class.java,
            String::class.java
        )
        return Moshi.Builder().build().adapter<Map<String, String>>(type).toJson(map)
    }
}

Upvotes: 0

douglas
douglas

Reputation: 116

Here is a Java equivalent for Jakub's answer, using Android SDK's TextUtils as it provides a clean join equivalent to Kotlin's joinToString() but Apache Commons' StringUtils will suffice too.

@TypeConverter
public String fromStringMap(Map<String, String> value) {
    final Map sortedMap = new TreeMap<>(value);
    return TextUtils.join(",",sortedMap.keySet())
            .concat("<divider>")
            .concat(TextUtils.join(",",sortedMap.values()));
}

@TypeConverter
public Map<String,String> toStringMap(String value){
    final String[] keysValsSep= value.split("<divider>");
    final String[] keys= keysValsSep[0].split(",");
    final Iterator<String> valuesIterator= Arrays.asList(keysValsSep[1].split(",")).iterator();
    final Map<String,String> strMap= new HashMap<>();

    for(String key : keys){
        strMap.put(key,valuesIterator.next());
    }
    return strMap;
}

Upvotes: 0

Hussien Fahmy
Hussien Fahmy

Reputation: 1295

you can make use of Kotlin as functional programming to simplify your code

private const val KEY_VALUE_SEPARATOR = "->"
private const val ENTRY_SEPARATOR = "||"

class Converters {

    /**
     * return key1->value1||key2->value2||key3->value3
     */
    @TypeConverter
    fun mapToString(map: Map<String, String>): String {
        return map.entries.joinToString(separator = ENTRY_SEPARATOR) {
            "${it.key}$KEY_VALUE_SEPARATOR${it.value}"
        }
    }

    /**
     * return map of String, String
     *        "key1": "value1"
     *        "key2": "value2"
     *        "key3": "value3"
     */
    @TypeConverter
    fun stringToMap(string: String): Map<String, String> {
        return string.split(ENTRY_SEPARATOR).map {
            val (key, value) = it.split(KEY_VALUE_SEPARATOR)
            key to value
        }.toMap()
    }
}

you can test this code by yourself in kotlin playground from here

Upvotes: 0

Jakub Kostka
Jakub Kostka

Reputation: 855

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.

Upvotes: 4

RobertoAllende
RobertoAllende

Reputation: 9528

Following brian-acker's response, a similar TypeConverter in Kotlin using Moshi would be something like:

class Converters {

  private val mapAdapter =
      Moshi.Builder().add(SingleToArray.Adapter.FACTORY).build().adapter<Map<String, String>>(
          Map::class.java)

  @TypeConverter
  fun fromJson(aString: String?): Map<String, String>? =
      aString?.let { mapAdapter.fromJson(it) }

  @TypeConverter
  fun toJson(aMap: Map<String, Any>?): String? =
      aMap?.let { anyMapAdapter.toJson(it) }

}

Upvotes: 0

Brian Acker
Brian Acker

Reputation: 323

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);
    }
}

Upvotes: 15

Related Questions