Is it possible to "inject" a custom value into a d...
# serialization
s
Is it possible to "inject" a custom value into a deserialized class? I'm looking to make my HTTP client/API client accessible in my models so that methods can be called on those models directly instead of having to go through the client each time.
Copy code
@Serializable
data class Item(val id: Int, val client: ApiClient)
In the quick example above I'd want the ID to be deserialized from the payload, but somehow need a way to pass in the ApiClient myself. Placed it as a constructor param as an example but it doesn't have to be. Creating a nullable
@Transient
property and manually filling in the field after each deserialization is possible I guess but really ugly imo and I'd rather avoid it. Was hoping there's a better way here?
d
I don’t think this is a good design, as it mixes data-transfer with business logic. It’s generally better to keep these things separated, to reduce complexity and improve unit testablilty.
s
In terms of unit testing I feel like you could do the exact same in both cases as one is just a shortcut to the other. However my main thing is I feel that
Item.method()
and
Item1.method(Item2)
are both a lot nicer to work with from a user-perspective than
ApiClient.item_method(Item1, Item2)
d
It’s kind of difficult to reason about the right approach in the abstract here, but my other suggestion would be to have a wrapper class that takes both the DTO, and the service, and makes the appropriate calls there.
s
That's also possible, but makes the implementation a lot more verbose, and requires the main DTO to have all its fields copy-pasted which is very boilerplate-y My question was about a way to inject this value in there to automate the whole process and abstract this away. I was already aware I could manually create a wrapper class and do it myself.
d
Why would it require all the fields to be copy-pasted?
Copy code
class MyWrapper(val service: MyService, val data: MyData) {
   fun doTheThing(): MyOtherWrapper {
      val otherData: MyOtherData = service.doTheThing(data)
      return MyOtherWrapper(service, otherData)
   }
}
s
Because I want the data inside of it to be accessible, going through an extra .data attribute every time defeats the whole purpose of this shortcut in the first place…
d
Seems like you've decided to make something more complicated than it needs to be 😉
s
Not at all
d
Mixing business logic with DTOs will eventually cause headaches. It might seem like a good idea now, but it usually isn't.
Even putting the business logic in an "ApiClient" is breaking SOLID principals.
A well-layered design would have Service->ApiClient->DTO
s
that's what I have already
The situation is
Copy code
data class MyData(val someField)

// Usage
SomeService/Client.someMethod(someObject)
println(someObject.someField)
with your thing this would be
Copy code
someObject.someMethod()
println(someObject.data.someField)
which defeats the purpose entirely, it's not a shortcut as it moved the extra fluff to attributes instead of methods
d
You're the one that wanted the data and methods to be closer. I would go for the first approach myself.
s
I already have the first case. I'm the one that asked if I can inject this client in there to have BOTH advantages and none of the disadvantages
d
You already came up with your answer, and then dismissed it.
s
Yeah because it's ugly and hacky, so I asked if there was a better way to do it
d
Yes, the better way is "don't"
Alternatively, you'd have to write custom KSerializer implementations inside your service and inject the value in there, but then you will need to add the boilerplate to handle all of the fields, which you've already stated you don't want to do.