Reputation: 3844
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
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
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
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
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
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
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