How to convert Reactor Map<Long, Mono<String>> to Mono<Map<Long, String>> - mono

How to convert a map of Monos to a Mono emitting a Map containing each value?
private Mono<Map<Long, String>> convertMonoMap(Map<Long, Mono<String>> monoMap) {
// TODO what comes here?
}
#Test
public void testConvert() {
Map<Long, Mono<String>> input = Map.of(1L, Mono.just("1"), 2L, Mono.just("2"));
Map<Long, String> expected = Map.of(1L, "1", 2L, "2");
Mono<Map<Long, String>> output = convertMonoMap(input);
StepVerifier.create(output).expectNext(expected).verifyComplete();
}

The full solution would be:
private Mono<Map<Long, String>> convertMonoMap(Map<Long, Mono<String>> monoMap) {
return Flux.fromIterable(monoMap.entrySet())
.flatMap(entry -> entry.getValue().map(
value -> Tuples.of(entry.getKey(), value))
).collectMap(Tuple2::getT1, Tuple2::getT2);
}
The flatMap is used to unwrap the entry from Flux<Entry<Long, Mono<String>>> to Flux<Tuple<Long, String>>. Finally, there are multiple collect operators available to get from reactive sequence to non-reactive sequence, e.g. collectMap.

Related

kotlin idiomatic way to make it simpler when pass in a nullable mutableMap

converting from java to kotlin
java code
public void logEvent(String eventName, #Nullable Map<String, String> customParams) {
if (customParams == null) {
customParams = new HashMap<>();
}
customParams.put(OTHER_REQUIRED_KEY, OTHER_REQUIRED_VALUE);
service.doLogEvent(eventName, customParams);
}
kotlin code
fun logEvent(eventName: String, customParams: Map<String, String>?) {
var customParamsMap = HashMap<String, String>()
if (customParams != null) {
customParamsMap.putAll(customParams)
}
customParamsMap[OTHER_REQUIRED_KEY] = OTHER_REQUIRED_VALUE
service.doLogEvent(eventName, customParamsMap)
}
the kotlin code will create the temp map regardless if the passed in map is null or not.
is there a better way to avoid this map creation?
This is as simple as:
fun logEvent(eventName: String, customParams: MutableMap<String, String>?) {
val customParamsMap = customParams ?: mutableMapOf()
...
}
Or you can specify a default value for customParams:
fun logEvent(eventName: String, customParams: MutableMap<String, String> = mutableMapOf()) {
...
}
Note that in both examples I changed the type of customParams to MutableMap. This is a direct equivalent of the Java code. If it requires to be a read-only Map then you actually need to copy elements to a new map:
fun logEvent(eventName: String, customParams: Map<String, String>?) {
val customParamsMap = customParams?.toMutableMap() ?: mutableMapOf()
...
}
The other answer is great for a one-to-one translation of the Java code. But if you are able to change the signature, you can make it more user friendly in Kotlin by making the parameter optional rather than nullable.
fun logEvent(eventName: String, customParams: MutableMap<String, String> = mutableMapOf()) {
// no need for "customParamsMap`. Use "customParams" directly.
// ...
}
But either way, in my opinion it is not user friendly to require the passed map to be mutable. And presumably there aren't so many possible parameters that we are worried about the performance of copying them over. I would write the function like this, simple and flexible:
fun logEvent(eventName: String, customParams: Map<String, String> = emptyMap()) {
service.doLogEvent(eventName, customParams + (OTHER_REQUIRED_KEY to OTHER_REQUIRED_VALUE))
}

How to parse kafka message in KafkaSpout and set in tuple value

I am trying to read kafka messages from KafkaSpout and set tuple values from json that are parsed from that message. Actually, I am creating an additional Bolt that parses a tuple field called "value" with json string from KafkaSpout. Is it possible to set these values in Spout?
class ScanConfigKafkaSpout(kafkaUrl: String, kafkaGroup: String, kafkaTopic: String) : KafkaSpout<String, String>(
KafkaSpoutConfig
.builder(kafkaUrl, kafkaTopic)
.setProp(KEY_KAFKA_GROUP, "grp1")
.setProcessingGuarantee(KafkaSpoutConfig.ProcessingGuarantee.AT_MOST_ONCE)
.build()
), ComponentId {
override fun open(conf: MutableMap<String, Any>?, context: TopologyContext?, collector: SpoutOutputCollector?) {
try {
logger.debug("<${id()}> Opening ScanConfigKafkaSpout with ${conf.toString()}")
super.open(conf, context, collector)
logger.debug("<${id()}> ScanConfigKafkaSpout opened")
} catch (t: Throwable) {
logger.error("<${id()}> Error during opening CrawlScanConfigKafkaSpout", t)
}
}
override fun id(): String = SCAN_CONFIG_KAFKA_SPOUT
companion object {
private val logger = LoggerFactory.getLogger(ScanConfigKafkaSpout::class.java)
}
}
You probably need to implement the method declareOutputFields(OutputFieldsDeclarer declarer from IComponent.
It is used by Storm to serialize your attribute values and tuple configurations.
As stated here in the section Data Model , it says:
Every node in a topology must declare the output fields for the tuples it emits.
There is also a java example given for that method.
#Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("double", "triple"));
}

How to define the map as a constant object, not in a method which will be created again and again

This is my method which used to populate a map, and there's other data will be added in the future.
object ConfigurationService {
fun populateCache(client: RedissonClient): RMap<String, String> {
val map = client.getMap("map")
map["k1"] = "v1"
map["k2"] = "v2"
....
return map
}
}
The problem is, each time when I calling this method in my main function, it will re-create the same map content again and again, is there a way to define it as a constant map object, not in a method.
This is what I did, is that correct?
class ConfigurationService(client: RedissonClient) {
val map = client.getMap("map")
fun populateCache(): RMap<String, String> {
map["k1"] = "v1"
map["k2"] = "v2"
....
return map
}
}
One option is to extract the constant part as a property on your object:
object ConfigurationService {
private val fixedMapContent = mapOf(
"k1" to "v1",
"k2" to "v2",
)
fun populateCache(client: RedissonClient): RMap<String, String> {
val map = client.getMap("map")
map.putAll(fixedMapContent)
return map
}
}
But I don't think that's what you're looking for.
If the client to use is always the same, another option is to inject that client into ConfigurationService (which will not be an object anymore, you'll need to inject it as well):
class ConfigurationService(private val client: RedissonClient) {
val cache by lazy {
val map = client.getMap("map")
map["k1"] = "v1"
map["k2"] = "v2"
....
map
}
}
If you want to control when the cache is initially populated (instead of lazily on first access), you can also do this:
class ConfigurationService(client: RedissonClient) {
val cache = client.getMap("map")
fun populateCache() {
cache["k1"] = "v1"
cache["k2"] = "v2"
....
}
}

How to merge nested Maps in Kotlin

I'm trying to merge values from two maps in Kotlin, but I can't seem to find a solution online to do so. For example, I have these two Map Objects that have nested maps as shown below.
first: {settings={exit={exitMessage=message0}}, 2=2}
second: {settings={exit={differentMessage=message1, secondMessage=message2}}, 2=zebra}
As you can see, there are some keys that are the same(like settings and exit), but sometimes the keys are different. I'm wondering how I can merge the two Map objects so the end result is like
{settings={exit={exitMessage=message0, differentMessage=message1, secondMessage=message2}}, 2=zebra}
We can't simply "add" the two maps together, because Kotlin will override the Map objects
For example, if we add the two objects up above, as first + second, the end result will just be
{settings={exit={differentMessage=message1, secondMessage=message2}}, 2=zebra}
as the exit map in second takes priority over the exit map object in first.
You can try flattening both maps first:
settings.exit.exitMessage=message0
2=2
settings.exit.differentMessage=message1
settings.exit.secondMessage=message2
2=zebra
Then after merging the maps you end up with this:
settings.exit.exitMessage=message0
settings.exit.differentMessage=message1
settings.exit.secondMessage=message2
2=zebra
And then just rebuild the map again by exploding the keys.
Full code sample here: https://pl.kotl.in/p9BaUVOF3
Flattening a map
fun flatten(source: Map<String, Any>, target: HashMap<String, Any>, prefix: String = "") {
for (entry in source) {
var fullKey = if (prefix.length > 0) prefix + "." + entry.key else entry.key
if (entry.value is Map<*, *>) {
flatten(entry.value as Map<String, Any>, target, fullKey)
continue
}
target.put(fullKey, entry.value)
}
}
Exploding a flattened map
fun explode(source: Map<String, Any>, target: HashMap<String, Any>) {
for (entry in source) {
var targetMap = target
var keySegments = entry.key.split(".")
// Rebuild the map
for (keySegment in keySegments.dropLast(1)) {
if (targetMap.containsKey(keySegment)) {
var value = targetMap.get(keySegment)
if (value is Map<*, *>) {
targetMap = value as HashMap<String, Any>
continue
}
}
var newNestedMap = HashMap<String, Any>()
targetMap.put(keySegment, newNestedMap)
targetMap = newNestedMap
}
// Put values into it
targetMap.put(keySegments.last(), entry.value)
}
}

Map values of JsonNode arrays in Kotlin

I have two JsonNode objects which are arrays returned from API calls and look like this
exportedNodeArray:
[
{"key":"111", "value":"aaa"},
{"key":"222", "value":"bbb"},
{"key":"333", "value":"ccc"}
]
localNodeArray
[
{"key":"999", "value":"aaa"},
{"key":"888", "value":"bbb"},
{"key":"777", "value":"ccc"}
]
The required output is a Map of any keys which correspond to the same values in each array. The values are guaranteed to be unique within an array.
"111"="999"
"222"="888"
"333"="777"
This function returns the correct result, but seems like a very in-elegant way to do it.
fun mapIds(exportedNodeArray: JsonNode, localNodeArray: JsonNode) : MutableMap<String, String?> {
val localMap = mutableMapOf<String, String>()
localNodeArray.forEach {
localMap[it["value"].asText()] = it["key"].asText()
}
val idMap = mutableMapOf<String, String?>()
exportedNodeArray.forEach {
idMap[it["key"].asText()] = localMap[it["value"].asText()]
}
return idMap
}
I am new to Kotlin, and would like to understand a more functional approach. Especially if there is a way to access elements of a JsonNode by attribute value, and accomplish this in a single loop or map call.
If you define one more function, it could look like this:
fun mapIds(exportedNodeArray: JsonNode, localNodeArray: JsonNode) : Map<String, String> {
val localConverted = convert(localNodeArray)
return convert(exportedNodeArray)
.filterKeys(localConverted::containsKey)
.map { it.value to localConverted.getValue(it.key) }
.toMap()
}
fun convert(node: JsonNode): Map<String, String> = node.associate {
it["value"].asText() to it["key"].asText()
}