Question(s) about delegates, extension functions and generics

Hi there! I am currently tackling a rather common problem: when deserialising some JSON request in a web service, I need to be able to distinguish between an absent property in the request, and a property that was explicitly set to null in the request.

There are several solutions to this problem, which broadly fall into two categories: 1) wrap the value in some custom type and write custom deserialisers, validation and other logic for this wrapper type, or 2) do some extra bookkeeping at the point you set(...) the value, so you can track that it has indeed been set by the deserialising logic.

Due to the constraints I am working with, I can’t really write custom deserialisers, so I am restricted to option 2.

After some experimenting with biolerplaterrific solutions, I stumbled upon delegates, which so far seem to be a nice solution, but I’m stumbling “at the finish line”, by a rather ugly method signature I can’t seem to get rid of. It is possible I am running into the limits of the Java/Kotlin type system, but I’d like to know if there isn’t some clever trick I am missing.

Currently I have the following:

For the request body:

interface DeserializedRequestBody {
  fun registerDeserialization(property: KProperty<*>)
  fun hasDeserializedField(property: KProperty<*>): Boolean
}

abstract class DefaultDeserializedRequestBody : DeserializedRequestBody {
  private val deserializedFields: MutableSet<KProperty<*>> = mutableSetOf()

  override fun registerDeserialization(property: KProperty<*>) {
    deserializedFields.add(property)
  }

  override fun hasDeserializedField(property: KProperty<*>) =
    property in deserializedFields
}

For the DeserialzedFieldDelegate:

 class DeserializedFieldDelegate<T, R : DeserializedRequestBody> {

  private var backingField: T? = null

  operator fun getValue(thisRef: R, prop: KProperty<*>): T? {
    return backingField
  }

  operator fun setValue(thisRef: R, prop: KProperty<*>, value: T?) {
    backingField = value
    thisRef.registerDeserialization(prop)
  }
}

And then I have this inline function:

inline fun <reified T : DeserializedRequestBody, V> T.deserializedValueOfOrDefault(
    property: KProperty1<T, V>,
      default: V?
    ): V? {
      return if (this.hasDeserializedField(property)) {
        property.get(this)
      } else {
        default
      }
    }

I can currently use these in the following way:

class FooRequestBody : DefaultDeserializedRequestBody() {
  @get:Length(min = 0, max = 255)
  var title: String? by DeserializedFieldDelegate()

  var number: Int? by DeserializedFieldDelegate()
}

class Foo(
  var title: String = "",
  var number: Int = 0
)

class FooService {
  fun updateFoo(oldFoo: Foo, request: FooRequestBody) {
    val newFoo: Foo = Foo(
      title = request.deserializedValueOfOrDefault(FooRequestBody::title, oldFoo.title)!!,
      number = request.deserializedValueOfOrDefault(FooRequestBody::number, oldFoo.number)!!
    )

    // repository.save(newFoo)
  }
}

The neat thing about this is that, as far as all the old, battle-tested java frameworks are concerned, those delegated fields are just ordinary Strings and Ints (by virtue of their getters and setters). As an example, there is the @get:Length(min = 0, max = 255) validation annotation on the title field in the request, and aside from needing the get: bit mixed in there, that just works. Thanks, Kotlin.

However, making the perfect the enemy of the good is practically my middle name, and so I am still quite annoyed by this syntax:

title = request.deserializedValueOfOrDefault(FooRequestBody::title, oldFoo.title)!!

More specifically, the FooRequestBody::title property passing as a function argument seems pretty ugly over here in our nicely encapsulated OO-land.

Ideally (imho, obviously), I could do something like this:

title = request.title.valueOfOrDefault(oldFoo.title)!!

However, I’ve been poking at delegates, extension functions and generics for a couple of days now, and I cannot seem to take this final hurdle:

  • Creating an extension inline fun <reified T> SomeDelegate<T>.someFunction(bar: Bar) does not permit me to call someFunction on a value: T by SomeDelegate() property. value is apparently never a SomeDelegate , it is always a T
  • Creating an extension function fun Any.someFunction(bar: Bar) and then somehow using reflection to check at runtime if the called on property is actually delegated by… honestly I don’t even know how this sentence would end, it just feels wrong and stupid and I will end up creating Spring if I continue on this path.
  • Actually do have e.g. title be of some DeserializationWrapper<String> type, and then somehow making it so (through a different kind of delegate?) that I can just read and write Strings to it… I’ve been toying with the provideDelegate function to try and compute a delegate for my wrapper that somehow allows it to read and write values of the wrapped types transparantly, while also being able to have extra functionality on the wrapper… No success so far, and explaining it like this makes it sound like I want to have a value that is two types simultaneously… which kinda sums it up I guess :smiley: But only in a syntactic-sugar kind of way so I’m not sure if it is actually definitely impossible…

Any hints or insights very much appreciated!

1 Like

You should be able to do something like

class FooRequestBody : DefaultDeserializedRequestBody() {
  val titleDelegate = DeserializedFieldDelegate()
  @get:Length(min = 0, max = 255)
  var title: String? by titleDelegate
}

Then you can define deserializedValueOfOrDefault as a extension for the delegate and call it like this

title = request.titleDelegate.deserializedValueOfOrDefault(oldFoo.title)

Not sure if you need to pass this to DeserializedFieldDelegate constructor. There might be a way to get that reference but worst case you have to use val titleDelegate = DeserializedFieldDelegate(this) instead.

Not sure if this is the best solution but it’s the best I could come up with right now.

Oh, that’s clever. I had seen the property: Type by someOtherProperty delegation syntax, but hadn’t really realised I could use it here.

It’s still a bit boilerplate-y, but at least its more localised, and maybe there’s some other tricks I could do here.

Thanks!

Also, since all your properties will have the same type, you could have a syntax like this:

request["title"].valueOfOrDefault(oldFoo.title)!!

Simply, this can be implemented with an operator fun get(name: String) defined in your DefaultDeserializedRequestBody that simply loops over all the properties in its deserialized fields and returns the one matching the name. Then just simply define an extension function on KProperty named valueOfOrDefault and that should do the trick I think.

You loose type safety that way though. It should work but losing all type information is not worth it IMO.

Yes, the refactor that created this question was done because that was essentially what we were doing before: store some string key in a Set for each actually deserialized property and match on that. But this is not type-safe or refactor-friendly, and resulted in a lot of escaped defects.

I tried playing around with the titleDelegate / title split method, but in practice this seems to end up leaking even more implementation details than the call-by-KProperty method I started with. It’ possible its a “naming things” kind of problem, but the fact remains you’ve got two properties to use for the same field, one to which you can read/write only Strings/Integers/…, and one that you can read/write only DeserializedFieldDelegates . It’s all strongly typed, but the design re-introduces possible escaped defects by wrongly using the interfaces.

It’s possible I just need to throw more encapsulation at this problem and make the actual FooRequest smarter (the request classes were just basic data classes before this, which was probably naive), with custom methods like getDeserializedTitleOrDefault(...) and getDeserializedNumberOrDefault(...) etc… Quite a bit of boilerplate (it’s essentially just currying at that point), but at least it doesn’t leak implementation details.

Consider this alternative approach:

Every time a request body setter is called, store in a list the corresponding action you’ll want to take upon the target you are updating. The pattern can be wrapped into a delegate.

Notice this drops the concept of a FooService or any deserializedValueOfOrDefault calls entirely.

abstract class DeserializedRequestBody<R, T> {
    // List of actions that will actually update your target instance
    private val updates = mutableListOf<T.() -> Unit>()

    //Does the actual update on the target
    fun applyUpdatesTo(target: T) {
        updates.forEach { target.it() }
    }

    // The actual delegate that adds update actions to the list when the setter is called
    protected fun <V> deserializeAs (update: T.(V) -> Unit) = object : ReadWriteProperty<R, V> {
        override fun getValue(thisRef: R, property: KProperty<*>): V {
            throw IllegalStateException("Only use the setter for deserialization")
        }

        override fun setValue(thisRef: R, property: KProperty<*>, value: V) {
            updates += { update(value) }
        }
    }
}

class Foo(
        var title: String = "",
        var number: Int = 0
)

class FooRequestBody : DeserializedRequestBody<FooRequestBody, Foo>() {
    var title: String by deserializeAs { title = it }
    var number: Int by deserializeAs { number = it }
}
1 Like

Very interesting! I like the approach and the syntax example of passing functions to delegates is very helpful, as I’ve struggled a bit with that in the past.

I don’t think it would actually work in this case, however. For one, there’s the obvious problem with the runtime exception on the getter. I’m not wholly against the idea (I obviously prefer compile-time checks, but I’ll settle for fast and loud failure at runtime where necessary), but the thing is that Java validation libraries need to work on the getter when using delegates, and so this would break validation.

Secondly, the actual updates can be fairly complex. This example works perfectly for any primitive or simple record type-ish values, but sometimes we get e.g. a list of DB id’s in the request body. Resolving these for updates would mean that either FooRequest or Foo suddenly needs to be aware of things like services and/or repositories, which seems like a SoC nightmare waiting to happen.

Still, great stuff, I learned new things from this, thank you for posting it! :smiley:

1 Like