Kotlin allows us to extend a class with new functionality at compile-time without having to inherit from any parent class using a extension functions.
In Java, only mechanizm to extend a class at compile-time is to extend from another known class having the functionality (non-private methods / attributes) available to the inheriting classes. There are many options extend functionality of instance by encapsulating it at run-time, using design patterns such as Decorator, Proxy, instance of check and casting. We are not going to discuss them here.
Kotlin supports both extension functions and extension properties. You can find more on these special declarations called extensions on Kotlin’s official documentation here.
We will take a simple and useful example, very common class, JsonNode
from fasterxml.jackson.
. Transforming JsonNode
(tree style) object, is not convenient as modifying a Map<String, Any>
. However, ObjectMapper
comes with built in functions to convert JsonNode
from / to a Map<String, Any>
. We will extend its functionality of JsonNode
to allow transforming of a JsonNode
instance by setting properties as we would do on a Map<String, Any>
object.
Let us magically extend JsonNode
class with two extension functions.
JsonNode.toMap(): MutableMap<String, Any>
, which receives an instance JsonNode
as (this) and can convert the JsonNode
tree into Map<String, Any>
using already available ObjectMapper
functionality.JsonNode.transform(fn: MutableMap<String,Any>.()-> Unit) :JsonNode
, which receives an instance of JsonNode
(jsonNode) as (this) and a Function / Unit (fn) as an an argument to set properties on Map<String, Any>
as its receiver.See the implementation below. When jsonNode.transform(fn) is called, it first uses JsonNode.toMap()
extension function previously defined to create a Map<String, Any>
(map) from instance of JsonNode
(jsonNode) it was called upon, and then calls the map.fn() [aka. map.also(fn)], where (fn) was passed in as the unit function applying all the modifications to the (map) built from (jsonNode) and finally return the resulting Map<String, Any>
converting it to a JsonNode
again using the ObjectMapper
private fun jsonNode(): JsonNode = mapper.readValue(
StringReader("""
{
"name": "Simar",
"country": "Canada"
}
""") // triple quoted for multi-line strings
)
fun JsonNode.transform(fn: MutableMap<String, Any>.() -> Unit): JsonNode =
toMap().also(fn).let { _mapper_.valueToTree(_it_) } fun JsonNode.toMap(): MutableMap<String, Any> =
(_mapper_.readValue(StringReader(toString())) as MutableMap<String, Any>)
@Test
public fun tesJsonTransformation()
{
val jsonNode = jsonNode();
assertEquals("Simar", jsonNode.get("name").textValue())
assertEquals("Canada", jsonNode.get("country").textValue())
val newJsonNode: JsonNode = jsonNode.transfrorm({ set("name", "Paul")
remove("country")
});
assertEquals("Paul", newJsonNode.get("name").textValue())
assertNull(newJsonNode.get("country"))
}
}