Kotlin parse 2 type of field(string, object) like on Swift - kotlin

on IOS i have a code for parsing nested json. This json can contains string and object. It looks like this(Swift):
struct Place: Codable {
let value: [Dependent]?
}
enum Dependent: Codable {
case object(Place)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let objectVal = try container.decode(Place.self)
self = .object(objectVal)
} catch DecodingError.typeMismatch {
let stringVal = try container.decode(String.self)
self = .string(stringVal)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .object(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
But its to hard understand how i can create the same parser for Kotlin, i did code like this:
data class Place(
val value: List<Dependent>
)
sealed class Dependent {
data class OBJECT(val value: Place): Dependent()
data class STRING(val value: String): Dependent()
}
It doesnt work, i feel i missed something
UPD:
The josn looks like this:
{
"value": [
"123123123",
{"value": ["123123", "123123", {"value":["123"]}, "123"]}
]
}

Related

Jackson ContextualDeserializer cannot get the contextualType when decoding a generic Kotlin class

I have a custom implementation of a JsonDeserializer which implements a ContextualDeserializer in order to deserialize a generic class. Everything works fine until the generic class itself has a property which is also a generic class.
For demo purposes, I simplified the code to the minimum
My class is
data class MyObject<out Content>(
val content: Content
)
Deserializing MyObject<String> works:
{
"content": "Hello"
}
but deserializing MyObject<MyObject<String>>> does not:
{
"content": {
"content": "Hello"
}
}
The custom deserializer is the following. The issue seems to be that contextualType.containedType is returning null.
I believe this issue is in the line
val content = ctxt.readTreeAsValue(contentNode, contentType!!.rawClass)
because the .rawClass does not have any additional information about the generic type. So the content is deserialized as just MyObject instead of MyObject<String>.
class MyObjectDeserializer : JsonDeserializer<MyObject<*>?>(), ContextualDeserializer {
private var contentType: JavaType? = null
override fun createContextual(ctxt: DeserializationContext?, property: BeanProperty?): JsonDeserializer<*> {
contentType = if (property == null)
ctxt!!.contextualType.containedType(0)
else
property.type.containedType(0)
if (contentType != null) {
println("${ctxt!!.contextualType} has contained type $contentType")
} else {
// Here is where the issue occurs
println("${ctxt!!.contextualType} does not have any contained types")
}
return this
}
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext): MyObject<*> {
val codec = p?.codec ?: throw NullPointerException()
val node = codec.readTree<JsonNode>(p)
val contentNode = node.get("content")
val content = ctxt.readTreeAsValue(contentNode, contentType!!.rawClass)
return MyObject(content)
}
}
and I register the deserialzers like so
fun ObjectMapper.registerMyDeserializers(): ObjectMapper {
val module = SimpleModule().also {
it.addDeserializer(MyObject::class.java, MyObjectDeserializer())
}
return this.registerModule(module)
}
The unit test the demonstrates the issue is the follow. The first test works fine, the second fails.
class ResponseParsingTests {
private val objectMapper = jacksonObjectMapper().registerMyDeserializers()
#Test
fun `parses String as content`() {
val json = """
{
"content": "Hello"
}
""".trimIndent()
val result = objectMapper.readValue<MyObject<String>>(json)
assertEquals("Hello", result.content)
}
#Test
fun `parses MyObject as content`() {
val json = """
{
"content": {
"content": "Hello"
}
}
""".trimIndent()
val result = objectMapper.readValue<MyObject<MyObject<String>>>(json)
assertEquals("Hello", result.content.content)
}
}
Please note that I'm aware the this example class does not require any custom deserialzer at all. However my real use case is a bit more complex and I need to use a custom deserializer because I'm publishing my code as part of a library which suppoorts multiple serialization frameworks (gson, jackson, kotlinx). So the serialization cannot be part of my actual class but rather in a separate one.

Should I get rid of big switch case?

I have a factory which includes many HTML attribute generators which returns one of them based on the type of attribute, so I wanted to see if there is a better way of doing this.
class AttributeHtmlGeneratorFactory {
fun create(property: String): AttributeHtmlGenerator {
when (property) {
"animation" -> {
return AnimationHtmlGenerator()
}
...
"left", "top" -> {
return PositionHtmlGenerator()
}
...
"scaleX" , "scaleY", ... , "direction" -> {
return UnusedAttributesHtmlGenerator()
}
this when switch has like 20 switch cases in it.
this is the interface which all these classes are using
interface AttributeHtmlGenerator {
fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel ): String
}
and this is where and how I'm using all of these:
var result = ""
HtmlComponentDataModel::class.memberProperties.forEach { member ->
val generator = AttributeHtmlGeneratorFactory().create(member.name)
result = result.plus(generator.generateHtml(member, component))
}
return result
also, this is a simple implementation of the interface:
class ButtonFillHtmlGenerator : AttributeHtmlGenerator {
override fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel): String {
var result = ""
member.get(component)?.let {
result = result.plus("background-color:${it};")
}
return result
}
}
is there anyway to make this better?
If you just want to reformat the when statement, I suggest you you do like this:
fun create(property: String): AttributeHtmlGenerator = when (property)
{
"animation" -> AnimationHtmlGenerator()
"left", "top" -> PositionHtmlGenerator()
"scaleX", "scaleY", "direction" -> UnusedAttributesHtmlGenerator()
else -> error("No generator found for property $property")
}
If you want to split this logic across modules, you would use a Map.
class AttributeHtmlGeneratorFactory {
private val generatorMap = mutableMapOf<String, () -> AttributeHtmlGenerator>()
init {
assignGeneratorToProperties("animation") { AnimationHtmlGenerator() }
assignGeneratorToProperties("left", "top") { PositionHtmlGenerator() }
}
fun create(property: String): AttributeHtmlGenerator {
return generatorMap[property]?.invoke() ?: error("No generator found for property $property")
}
fun assignGeneratorToProperties(vararg properties: String, provider: () -> AttributeHtmlGenerator) {
properties.forEach {
generatorMap[it] = provider
}
}
}
This way you can call assignGeneratorToProperties in parts of the code and thus split the initialization logic.
Performance-wise, when/if-else statements are really performant when you have a few cases but a HashMap outperforms them for a lot of elements. You decide what to use depending on your case.

Cannot invoke initializer for type 'TextField<_>' with propertyWrapper of UseDefaults

I am working on a SwiftUI screen that updates multiple values in the UserDefaults, to allow the app to persist basic settings. I am trying to use Combine and SwiftUI, as this is a native WatchOS app.
The basic View is giving me an error that I believe has to do with the propertyWrapper for UserDefaults, but as I have never worked with propertyWrappers (or Combine for that matter) I am un able to figure out how to fix this.
here's the property wrapper:
import Foundation
#propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
UserDefaults(suiteName: "group.com.my.app")!.value(forKey: key) as? T ?? defaultValue
} set {
UserDefaults(suiteName: "group.com.my.app")!.set(newValue, forKey: key)
}
}
}
As you can see it wraps all the Key Pairs for the UserDefaults.
My class is similarly very simple, consisting of two bools and a double
import SwiftUI
import Foundation
import Combine
class Setup: ObservableObject {
private var notificationSubscription: AnyCancellable?
let objectWillChange = PassthroughSubject<Setup,Never>()
#UserDefault("keyOpt1Enabled", defaultValue: false)
var opt1Enabled: Bool {
willSet{
objectWillChange.send(self)
}
}
#UserDefault("keyOpt2Enabled", defaultValue: false)
var opt2Enabled: Bool {
willSet{
objectWillChange.send(self)
}
}
#UserDefault("keyValueDouble", defaultValue: Double(0.00))
var someValueDouble: Double {
willSet{
objectWillChange.send(self)
}
}
init() {
notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
self.objectWillChange.send(self)
}
}
}
The problem is that in SwiftUI I am using a TextField to allow for entering and updating the double value
#ObservedObject var setup: Setup = Setup()
private var currencyFormatter: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .currency
return f
}()
var body: some View {
ScrollView{
HStack{
TextField(self.$setup.someValueDouble,
formatter: currencyFormatter,
placeholder: "0.00",
onEditingChanged: {_ in
print("editing changed")
},
onCommit: {
print("updated")
}
)
}
HStack{
Button(action: {
self.setup.opt1Enabled = false
self.setup.opt2Enabled = true
} ) {
Text(verbatim: "Opt 1")
.font(Font.system(size: 16, design: Font.Design.rounded))
}
.background(setup.opt1Enabled ? Color.blue : Color.gray)
.disabled(self.setup.opt1Enabled)
.cornerRadius(5)
Button(action: {
self.setup.opt1Enabled = true
self.setup.opt2Enabled = false
}) {
Text(verbatim: "Opt 2")
}
.background(setup.opt2Enabled ? Color.blue : Color.gray)
.disabled(self.setup.opt2Enabled)
}
}
}
}
The TextField then gives the message that the Generic parameter 'Label' could not be inferred. Xcode offers to "Fix" this but the end results is TextField which is obviously incomplete, but the only view in this whole program is "ContentView" which is invalid.
The problem is that TextField's initialiser takes first a String as it's placeholder and next the value it is bound to, so it should look roughly like this:
TextField("Placeholder",
value: self.$setup.someValueDouble,
formatter: currencyFormatter,
onEditingChanged: { _ in },
onCommit:{})
.keyboardType(.numberPad)
You might want to read more about TextFields and Formatter, because it seems to be a tricky subject.

How do we compose queries in Kotlin Exposed?

I would like to create something similar to Ruby's ActiveRecord Scopes using Kotlin Exposed.
For example I would like to break the following query up so that the first part acts like a scope.
This query returns what I want.
val m1 = Measurement.wrapRows(Measurements.innerJoin(Runs).select {
((exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})) and Measurements.name.eq("someName")
I would like to use this part as a scope:
val q1 = Measurements.innerJoin(Runs).select {
((exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
}))
}
and then be able to refine the query using the q1 "scope"
so something like this:
val q2 = q1.having { Measurements.name.eq("someName") } // which does not work
Ultimately I would like to push this down into either the Measurements object or the Measurement class so I can do something like this
Measurement.withDefaultRegion.where( Measurements.name.eq("someName")
I was able to get what I wanted by adding a couple of functions to the model's companion object.
The first one provides the "scope"
fun defaultRegion() :Op<Boolean> {
return Op.build {(exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})}
}
The second function does the query using the scope and any refinements passed in and returns a "collection" of objects.
fun withDefaultRegionAnd( refinedBy: (SqlExpressionBuilder.()->Op<Boolean>)) : SizedIterable<Measurement> {
return Measurement.wrapRows(Measurements.innerJoin(Runs).select(Measurement.defaultRegion() and SqlExpressionBuilder.refinedBy() ))
}
At the client level I can simply do this:
val measurements = Measurement.withDefaultRegionAnd { Measurements.name.eq("someName") }
Here are the nearly table object and entity classes:
object Measurements : IntIdTable("measurements") {
val sequelId = integer("id").primaryKey()
val run = reference("run_id", Runs)
// more properties
}
class Measurement(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Measurement>(Measurements) {
fun defaultRegion() :Op<Boolean> {
return Op.build {(exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})}
}
fun withDefaultRegionAnd( refinedBy: (SqlExpressionBuilder.()->Op<Boolean>)) : SizedIterable<Measurement> {
return Measurement.wrapRows(Measurements.innerJoin(Runs).select(Measurement.defaultRegion() and SqlExpressionBuilder.refinedBy() ))
}
}
var run by Run referencedOn Measurements.run
var name by Measurements.name
// more properties
}

How to improve my code to show a data array in a table, with TornadoFX?

This is my way to display an array of data:
private val data = observableArrayList(
arrayOf("AAA", "111"),
arrayOf("BBB", "222"),
arrayOf("CCC", "333")
)
class HelloWorld : View() {
override val root = tableview<Array<String>>(data) {
column("name") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[0])
}
column("value") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[1])
}
}
}
It works but the code is quite complex. Is there any better way to do it?
(Maybe define a class to hold the data will make it much simpler, but I just want to test some uncommon cases)
Update:
A complete demo project for this: https://github.com/javafx-demos/tornadofx-tableview-array-data-demo
Here is a simpler way of defining your columns:
class HelloWorld : View() {
override val root = tableview(data) {
column<Array<String>, String>("name", { it.value[0].toProperty() })
column<Array<String>, String>("value", { it.value[1].toProperty() })
}
}
That said, using a specialized data structure would yield less headache :)
An alternative approach would be to configure just the cell item type and then a value factory:
column("name", String::class) {
value { it.value[0] }
}
column("value", String::class) {
value { it.value[1] }
}