I'm trying to come up with a data model that allows me to do the following:
Define a type of Task and change its Status
This Status can be either InProgress or Completed
In the case of a completed Status, I want to be able to add data, that is specific to the Task that was completed.
Initially, I came up with this data model:
sealed class Task<R : TaskResult> {
abstract val status: TaskStatus<R>
data class A(
val data: String,
override val status: TaskStatus<NoResult>,
) : Task<NoResult>()
data class B(
val data: String,
override val status: TaskStatus<TaskBResult>,
) : Task<TaskBResult>()
}
sealed class TaskStatus<R : TaskResult> {
object InProgress : TaskStatus<NoResult>()
data class Completed<R : TaskResult>(val result: R) : TaskStatus<R>()
}
sealed class TaskResult {
object NoResult : TaskResult()
data class TaskBResult(val resultData: String) : TaskResult()
}
Here you have Task.A and Task.B, where:
A completed Task.A only accepts NoResult
A completed Task.B only accepts TaskBResult
However, when I run this:
fun main() {
val taskA = Task.A(
data = "data",
status = TaskStatus.InProgress
).copy(
status = TaskStatus.Completed(
result = NoResult
)
)
val taskB = Task.B(
data = "data",
status = TaskStatus.InProgress
).copy(
status = TaskStatus.Completed(
result = TaskBResult(
resultData = "resultData"
)
)
)
}
I get the following compile error for setting the initial status of Task.B:
status = TaskStatus.InProgress
Type mismatch.
Required: TaskStatus<TaskResult.TaskBResult>
Found: TaskStatus.InProgress
Does anyone know how to change the data model so I'm allowed to run this (or a very similar) main function?
This could work with a very little change: just make TaskStatus a covariant generic class and make InProgress a TaskStatus<Nothing>. This is a typical strategy you can use when you have "special case" objects that represent no state. After this change, your code should compile:
sealed class Task<R : TaskResult> {
abstract val status: TaskStatus<R>
data class A(
val data: String,
override val status: TaskStatus<TaskResult.NoResult>,
) : Task<TaskResult.NoResult>()
data class B(
val data: String,
override val status: TaskStatus<TaskResult.TaskBResult>,
) : Task<TaskResult.TaskBResult>()
}
sealed class TaskStatus<out R : TaskResult> {
object InProgress : TaskStatus<Nothing>()
data class Completed<R : TaskResult>(val result: R) : TaskStatus<R>()
}
sealed class TaskResult {
object NoResult : TaskResult()
data class TaskBResult(val resultData: String) : TaskResult()
}
fun main() {
val taskA = Task.A(
data = "data",
status = TaskStatus.InProgress
).copy(
status = TaskStatus.Completed(
result = NoResult
)
)
val taskB = Task.B(
data = "data",
status = TaskStatus.InProgress
).copy(
status = TaskStatus.Completed(
result = TaskBResult(
resultData = "resultData"
)
)
)
}
Related
I have a annotation AggregateId that could be set on method params and properties and that I will use to retrieve some id :
#Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
#Retention(AnnotationRetention.RUNTIME)
annotation class AggregateId
I wrote that test case :
data class Example(
#AggregateId
val id: UUID
)
class AggregateIdTests {
private val exUuid = UUID.fromString("bae30706-f949-4eb5-b091-d51a13ddc832")
#Test
fun test() {
val ex = Example(id = exUuid)
val id = resolve(ex)
Assertions.assertThat(id).isEqualTo(exUuid)
}
private fun resolve(target: Any): UUID? {
val prop = target::class.declaredMemberProperties.find {
it.findAnnotation<AggregateId>() != null
}
return prop?.getter?.call(target) as UUID?
}
}
That actually works.
But if I add this class in the code :
class TestClass {
fun aMethod(#AggregateId param: UUID) {
}
}
Suddently the AggregateId changes of target for the other class. Even though I didn't change the rest of the code. What is the explaination of this ?
(using kotlin 1.5)
I have this DTO in Kotlin
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
sealed class InspirationDTO {
abstract val id: Long
#JsonTypeName("DishOfTheDay")
data class DishOfTheDay(
#JsonProperty("dishOfTheDay")
val dishOfTheDay: DishOfTheDayDTO,
override val id: Long,
) : InspirationDTO()
#JsonTypeName("InspirationScreenContinuousSlider")
data class InspirationScreenContinuousSlider(
#JsonProperty("name")
val inspirationScreenContinuousSlider: InspirationScreenContinuousSliderDTO,
override val id: Long,
) : InspirationDTO()
#JsonTypeName("InspirationScreenLink")
data class InspirationScreenLink(
#JsonProperty("inspirationScreenLink")
val inspirationScreenLink: InspirationScreenLinkDTO,
override val id: Long,
) : InspirationDTO()
#JsonTypeName("InspirationScreenPagingSlider")
data class InspirationScreenPagingSlider(
#JsonProperty("inspirationScreenPagingSlider")
val inspirationScreenPagingSlider: InspirationScreenPagingSliderDTO,
override val id: Long,
) : InspirationDTO()
#JsonTypeName("InspirationScreenRecentlyViewedSlider")
data class InspirationScreenRecentlyViewedSlider(
#JsonProperty("inspirationScreenRecentlyViewedSlider")
val inspirationScreenRecentlyViewedSlider: InspirationScreenRecentlyViewedSliderDTO,
override val id: Long,
) : InspirationDTO()
#JsonTypeName("InspirationScreenTagsSlider")
data class InspirationScreenTagsSlider(
#JsonProperty("inspirationScreenTagsSlider")
val inspirationScreenTagsSlider: InspirationScreenTagsSliderDTO,
override val id: Long,
) : InspirationDTO()
}
in service I expect that DTO to save all data from it's classes
fun saveDishOfTheDay(inspirationDTO: InspirationDTO) = with(inspirationDTO) {
inspirationFacadeRepo.saveDish(DishOfTheDayEntity(id = id, title = inspirationDTO.DishofTheDay.title))
}
this part I need to get inspirationDTO.DishofTheDay.title
but inspirationDTO from this DTO can't see DishOfTheDay class and it's inner item title
like this
data class DishOfTheDayDTO(
val title: String,
)
so need to get all classes from that DTO and their fields to put in entity fields and save them.
Good morning. I'm trying to figure out how to deserialize a parameter but I can't find the solution. In practice, the response JSON should populate or not a field based on the value I get from another type of field, which is numeric and can be 0 or 1.
In particular (seeing following serialization) in MyData class there is a property called "mutable_value" that can be 1 or 0. when is 1 I must serialize b_property, else I must serialzie c_property.
I'm trying to achieve it but I don't know where I can do my case if 0 or 1...
The class I serialize is the following:
data class ClassResponseData(
#JsonProperty("code")
val code: String,
#JsonProperty("data")
val data: MyData,
) {
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
visible = true,
property = , // <-------------what I must do here?
defaultImpl = MyData.DefaultValue::class
)
#JsonSubTypes(
JsonSubTypes.Type(
value = MyData.BType::class,
name = "new_domain"
),
JsonSubTypes.Type(
value = MyData.CType::class,
name = "new_domain"
)
)
sealed class MyData{
data class DefaultValue(
#JsonProperty("b_value")
val bValue: String,
#JsonProperty("c_value")
val cValue: String,
#JsonProperty("mutable_value")
val mutableValue: Int
)
data class BType(
#JsonProperty("b_value")
val bValue: String,
#JsonProperty("mutable_value")
val mutableValue: Int
) : MyData()
data class CType(
#JsonProperty("c_value")
val cValue: String,
#JsonProperty("mutable_value")
val mutableValue: Int
) : MyData()
}
}
What I'm doing wrong?
What's a compact and simple way to implement variants of object going through different states?
The complication is that for every state a new properties added to the same object.
A task executed and follow through states Created / Waiting / InProgress possible way would be
to define following classes
class Task(
val type: String,
val make_request: () -> Void
)
class TaskWaitingExecution(
val task: Task,
val retry_count: Int
)
class TaskInProgress(
val task: TaskWaitingExecution,
val request_id: Int
) {
val timer = bon.timer()
}
And progress with steps
val task = Task("some_request', {})
val waiting = TaskWaitingExecution(task, 1)
val in_progress = TaskInProgress(waiting, 10)
But it's not ideal as accessing say type property would end up in
in_progress.task.task.type
Sadly there's no way to use implements by inhreitance implement PreviousTask by task
as it's not working for classes.
Is there a better way to express such logic?
Turn it inside out and put the status as a property of the Task:
class Task(
val type: String,
val make_request: () -> Void,
var status: TaskStatus = null,
)
To store the status-specific fields, a sealed class is perfect:
sealed class TaskStatus;
class WaitingExecution : TaskStatus (
val retry_count: Int
)
class InProgress : TaskStatus (
val request_id: Int
) {
val timer = bon.timer()
}
Usage:
val task = Task("some_request', {})
task.status = WaitingExecution(1)
task.status = InProgress(10)
You can do it with val status as well, but then you'd need to create a new Task object each time its status changes.
It's been a while since I last Kotlin'ed so there might be syntax errors, but I hope the idea is clear.
I'm trying to pass data class to the service-proxy of Vert.x like this:
data class Entity(val field: String)
#ProxyGen
#VertxGen
public interface DatabaseService {
DatabaseService createEntity(Entity entity, Handler<AsyncResult<Void>> resultHandler);
}
However, the service-proxy requires a DataObject as the parameter type.
Below are what I've tried so far.
First, I rewrite the data class as:
#DataObject
data class Entity(val field: String) {
constructor(json: JsonObject) : this(
json.getString("field")
)
fun toJson(): JsonObject = JsonObject.mapFrom(this)
}
Although this works, the code is redundant, so I tried the kapt with the following generator:
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
roundEnv.getElementsAnnotatedWith(ProxyDataObject::class.java).forEach { el ->
val className = el.simpleName.toString()
val pack = processingEnv.elementUtils.getPackageOf(el).toString()
val filename = "Proxy$className"
val classBuilder = TypeSpec.classBuilder(filename)
val primaryConstructorBuilder = FunSpec.constructorBuilder()
val secondaryConstructorBuilder = FunSpec.constructorBuilder().addParameter("json", JsonObject::class)
val secondaryConstructorCodeBlocks = mutableListOf<CodeBlock>()
el.enclosedElements.forEach {
if (it.kind == ElementKind.FIELD) {
val name = it.simpleName.toString()
val kClass = getClass(it) // get the corresponding Kotlin class
val jsonTypeName = getJsonTypeName(it) // get the corresponding type name in methods of JsonObject
classBuilder.addProperty(PropertySpec.builder(name, kClass).initializer(name).build())
primaryConstructorBuilder.addParameter(name, kClass)
secondaryConstructorCodeBlocks.add(CodeBlock.of("json.get$jsonTypeName(\"$name\")"))
}
}
secondaryConstructorBuilder.callThisConstructor(secondaryConstructorCodeBlocks)
classBuilder
.addAnnotation(DataObject::class)
.addModifiers(KModifier.DATA)
.primaryConstructor(primaryConstructorBuilder.build())
.addFunction(secondaryConstructorBuilder.build())
.addFunction(
FunSpec.builder("toJson").returns(JsonObject::class).addStatement("return JsonObject.mapFrom(this)").build()
)
val generatedFile = FileSpec.builder(pack, filename).addType(classBuilder.build()).build()
generatedFile.writeTo(processingEnv.filer)
}
return true
}
Then I can get the correct generated file by simply writing the original data class, but when I execute the building after cleaning, I still get the following error:
Could not generate model for DatabaseService#createEntity(ProxyEntity,io.vertx.core.Handler<io.vertx.core.AsyncResult<java.lang.Void>>): type ProxyEntity is not legal for use for a parameter in proxy
It seems that the generated annotation #DataObject is not processed.
So what should I do? Is there a better solution?