How to use JSONObject from Kotlin JS - kotlin-js

I am new to Kotlin JS.
I am trying to port the business logic of my android app to Kotlin JS.
My app uses the class org.json.JsonObject to do custom serialization. I can't use KotlinX serialization with annotations because my classes are inline and these annotations are not supported.
The Kotlin-JS project uses Gradle Kotlin DSL. I am specifying the dependency as "implementation ("org.json:json:20190722")". The compiler throws the error "unresolved reference" for anything from the library. I suspect it is not legal to link to a java library this way for Kotlin-JS. Is this true?
What is the best way to get an implementation of JsonObject into my app? Do I need to copy the source code into my project and compile it to JS myself?
Thanks for any help.

If you don't want to use the annotations in the kotlinx.serizalization library, you can still include it in your android and js platforms.
You will just need to construct and use the JsonObject type that is present on both platforms manually.
You can see the JsonObject definition in the library here:
https://github.com/Kotlin/kotlinx.serialization/blob/ffe216f0293231b09eea24a10aa4bc26ff6d5b90/runtime/commonMain/src/kotlinx/serialization/json/JsonElement.kt#L217
Here is an example of manually constructing a JsonObject using kotlinx.serialization classes
data class AnalyticsEvent(
val name: Event,
val columns: Map<Column, JsonPrimitive>? = null,
val properties: Map<Property, JsonElement>? = null
) : LoggingEvent() {
override fun toJson(): JsonObject {
val content: MutableMap<String, JsonElement> = mutableMapOf()
content[EVENT_NAME_KEY] = JsonPrimitive(name.actual)
val columnJSON = columns?.mapKeys { it.key.actual }
columnJSON?.let {
content[EVENT_COLUMNS_KEY] = JsonObject(columnJSON)
}
val propertiesJSON = properties?.mapKeys { it.key.actual }
propertiesJSON?.let {
content[EVENT_PROPERTIES_KEY] = JsonObject(propertiesJSON)
}
return JsonObject(content)
}

Related

Ktor - create List from Json file

i am getting error - This class does not have constructor at object : TypeToken<List<Todo>>() + object is not abstract and does not implement object member
data class Todo(
val identifier: Long ,
val name: String ,
val description: String
)
class DefaultData {
private lateinit var myService: MyService
#PostConstruct
fun initializeDefault() {
val fileContent = this::class.java.classLoader.getResource("example.json").readText()
val todos: List<Todo> = Gson().fromJson(fileContent, object : TypeToken<List<Todo>>() {}.type)
myService.createTodoFromJsontodos
}
}
how can I fix this?
Objective is : To be able to create an endpoint that can get data from json file via service
Is there is a full fledged example
Also how to create interfaces in Ktor? As I want to use Dependency Inversion to enable retrieving data from different sources
Kotlin has built-in util similar to TypeToken, so I suggest using it instead:
Gson().fromJson(fileContent, typeOf<List<Todo>>().javaType)
You will need to add a dependency to kotlin-reflect. typeOf() function is marked as experimental, but I use it for some time already and never had any problems with it.
Also, you said in your comment that this is a starter project. If you don't have any existing code already then I suggest to use kotlinx-serialization instead of Gson. It is a de facto standard in Kotlin.
You can easily take advantage of kotlinx-serialization.
Steps:
Add the kotlin serialization plugin in your build.gradle file
kotlin("plugin.serialization") version "1.5.20"
plugins {
application
java
id("org.jetbrains.kotlin.jvm") version "1.5.21"
kotlin("plugin.serialization") version "1.5.20"
}
Add the dependecy for serialization library
dependencies {
...
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
}
Decode your json string to corresponding object using Json decode method
val JSON = Json {isLenient = true}
val mytodos = JSON.decodeFromString(message) as List<Todo>

Ktor with Kmongo and kotlinx.serialization

I have a setup where I use KTor with KMongo and Kotlinx.Serialization.
The Kmongo part works, I can get and put my Class
#Serializable
data class Task(#ContextualSerialization #SerialName("_id") val _id : Id<Task> = newId(),
val title : String = "",
val description : String = ""
)
Into the database and retrieve it. That all works flawlessly.
But when I try to send that object through a rest call to the frontend, again with Kotlinx.Serialization.
get<Tasks>{ task ->
val dao by di().instance<Dao>();
val task = Task( title = "task1", description = "task1description");
val foundTask = dao.read(task);
if(foundTask != null){
call.respond(foundTask)
} else {
call.respond("didn't find anything")
}
}
It throws this expection:
kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class WrappedObjectId. For generic classes, such as lists, please provide serializer explicitly.
at kotlinx.serialization.PlatformUtilsKt.serializer(PlatformUtils.kt:21)
at kotlinx.serialization.modules.SerialModuleExtensionsKt.getContextualOrDefault(SerialModuleExtensions.kt:29)
at kotlinx.serialization.ContextSerializer.serialize(ContextSerializer.kt:29)
at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:227)
at kotlinx.serialization.builtins.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:72)
Now I figured out that this is because there are 2 instances of the kotlin.serialization json. and the one on KMongo does not share it's serializers with the other one.
so I added the serializers from KMongo to the other instance from Ktor
install(ContentNegotiation) {
json(
json = Json(DefaultJsonConfiguration.copy(prettyPrint = true), context = kmongoSerializationModule),
contentType = ContentType.Application.Json
)
}
and now I get
java.lang.ClassCastException: class kotlinx.serialization.json.internal.StreamingJsonOutput cannot be cast to class com.github.jershell.kbson.BsonEncoder (kotlinx.serialization.json.internal.StreamingJsonOutput and com.github.jershell.kbson.BsonEncoder are in unnamed module of loader 'app')
So my question is why is it happening and how to fix it?
Edit: This issue has been fixed.
I also posted this question on the KMongo github and basically got an instant response that this was fixed for Jackson but not yet for Kotlinx.serialization.
Someone is going to fix this.
Edit: It has been fixed

Should we avoid using typed arrays in Kotlin ? If yes, Is there any newer way to replace a typed array in Kotlin?

Here is my code where this class is used to inflate a view.
I am using typed array here. Is there any other way I could write this code
without using the typed array.
class CalculatorInputView(context: Context, attributeSet: AttributeSet) :
RelativeLayout(context, attributeSet) {
init {
LayoutInflater.from(context).inflate(R.layout.view_calculator_input,
this, true)
//attribute set
attributeSet.run {
val typedArray: TypedArray =
context.obtainStyledAttributes(
attributeSet,
R.styleable.CalculatorInputView
)
val textResource: String? =
typedArray.getString(R.styleable.CalculatorInputView_item_text)
}
}
}
Is there any other way I could write this code without using the typed array.
No, since the TypedArray class is responsible to contain the attributed values of Android resources.
However, you can use the Android KTX Core extensions in Kotlin to make it shorter:
context.withStyledAttributes(attributeSet, R.styleable.CalculatorInputView) {
val textResource = getString(R.styleable.CalculatorInputView_item_text)
}
Remember that you need to include them in your build.gradle:
implementation "androidx.core:core-ktx:1.2.0"

kotlin : cannot parse string to enum with Jackson

I use the lib jackson-module-kotlin to parse string of json into object.
My issue is when I parse a string into an enum , and when I launch with intellij, I have this stack trace:
Caused by: kotlin.reflect.jvm.internal.KotlinReflectionInternalError:
Reflection on built-in Kotlin types is not yet fully supported. No
metadata found for public final val name: kotlin.String defined in
kotlin.Enum[DeserializedPropertyDescriptor#212b316a]
I don't have this issue when I launch with maven.
I use kotlin 1.1.51, with intellij kotlin plugin 1.2.0-release-IJ2017.3-1, I target a JVM 1.8, and i use jackson-module-kotlin version 2.8.7
what should I do?
enum class CType { DEAL, FILE }
data class Code(val code: String, val type: CType)
fun testDeserialization() {
val mapper = jacksonObjectMapper()
// following line throws an exception:
mapper.readValue("""{"code":"A","type":"DEAL"}""", Code::class.java)
}
The only way I got it working is by adding additional #JvmStatic annotation. I had mapper.registerModule(new KotlinModule()); and all, nothing worked but this:
package nc.features.algo.model
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
enum class LHStatus (
#get:JsonValue val code: Int
) {
LH_POS_OVU_WAITING(1),
LH_NEG_OVU_WAITING(2),
;
companion object {
#JsonCreator
#JvmStatic
fun deser(code: Int?): LHStatus? {
if (code == null) return null
for (i in values()) {
if (i.code == code) return i
}
return null
}
}
}
You have to do a few things.
Update Jackson dependencies to the latest version (right now, 2.9.4).
Update Kotlin version to a version equal or greater than 1.3.0.
Be sure to add the following dependencies to your build.gradle:
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"
... then you call registerKotlinModule() on your Jackson ObjectMapper and the code of your enum should be just like this:
enum class CType(#get:JsonValue val value: String) {
DEAL("deal"),
FILE("file");
companion object {
#JsonCreator
fun fromString(value: String): CType? {
for (type in CType.values()) {
if (type.name.equals(value, true)) {
return gender
}
}
return null
}
}
}
Intellij is most likely using the kotlin compiler version 1.2.0 (from the plugin) and it doesn't seem to support reflection properly.
I suggest you do one of the following:
Upgrade your kotlin version in maven and the intellij kotlin plugin to newer versions (e.g. 1.2.30). If you do that, you also have to update jackson-module-kotlin to >= 1.9, since there is an incompatibility with kotlin 1.2 (see here).
Set the kotlin compiler version to 1.1 in Intellij Idea settings.
It is generally a good idea to use the same version of kotlin in Intellij Idea and maven/gradle.
You need to use the Kotlin module for Jackson that is compatible with Kotlin 1.2.x, this includes minimally these three versions of the module:
2.9.4.1 (works with any 2.9.x of Jackson, but best to use most recent)
2.8.11.1 (for Jackson 2.8.x)
2.7.9.1 (for Jackson 2.7.x)
Otherwise, you will run into a problem with library mismatches.
The jackson-module-kotlin homepage lists these as the current versions, but they are likely to change and you can check the various Maven repository search engines to see which library versions are available and which dependencies they have on Kotlin to find matching versions.
Also note you can import the extensions for the ObjectMapper class and use reified types, so instead of:
val something = mapper.readValue("""{"code":"A","type":"DEAL"}""", Code::class.java)
you would have:
val something: Code = mapper.readValue("""{"code":"A","type":"DEAL"}""")
or alternatively:
val something = mapper.readValue<Code>("""{"code":"A","type":"DEAL"}""")
It is usually bad to use the erased type (i.e. Whatever::class.java) since this does not work for anything with generic type parameters, and using reified types also works nicely when deserializing into collections.

Use Javascript libraries in Kotlin

The last time I used Kotlin was Dec 2015 when I used it to solve a couple of Project Euler problems.
This time I want to try its interoperability with Javascript. Now my question is, how do we import/use existing Javascript libraries in Kotlin?
I've seen some people using the native keyword, and I just want a brief explanation of it.
There's no native keyword anymore, there's #native annotation. Currently, it's working solution and you can use it with 1.0.x branch of Kotlin compiler. However, we are going do deprecate this annotation in favour of extern annotations, so be prepared to rewrite your code eventually for 1.1.x branch.
When you put #native annotation on a class or on a top-level function, two things happen:
Its body is not compiled to JavaScript.
Compiler references this class or function directly, without package name and mangling.
I think it's easier to explain by providing example of a JavaScript library:
function A(x) {
this.x = x;
this.y = 0;
}
A.prototype.foo = function(z) {
return this.x + this.y + z;
}
function min(a, b) {
return a < b ? a : b;
}
and a corresponding Kotlin declaration
#native class A(val x: Int) {
var y: Int = noImpl
fun foo(z: Int): Int = noImpl
}
#native fun min(a: Int, b: Int): Int = noImpl
Note that noImpl is a special placeholder that's required because of non-abstract functions required bodies and non-abstract properties require initializers. BTW, when we replace #native with extern, we'll get rid of this noImpl.
Another aspect of interoperation with JS libraries is including libraries via module system. Sorry, we don't have any solution right now (but are going to release it soon). See proposal. You can use the following workaround for node.js/CommonJS:
#native interface ExternalModule {
fun foo(x: Int)
}
#native fun require(name: String): dynamic = noImpl
fun main(args: Array<String>) {
val module: ExternalModule = require("externalModule")
module.foo(123)
}
where external module is declared like this
function foo(x) {
return x + 1;
}
module.exports = { foo : foo };
I added a simple barebone project as an example of how to do Kotlin2Js.
https://bitbucket.org/mantis78/gradle4kotlin2js/src
Here is the gradle file that is the main recipe.
group 'org.boonhighendtech'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.1.2-5'
repositories {
maven { url 'http://dl.bintray.com/kotlin/kotlin-dev/' }
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin2js'
repositories {
maven { url 'http://dl.bintray.com/kotlin/kotlin-dev/' }
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
build {
outputs.dir("web/")
}
build.doLast {
copy {
from 'src/main/webapp'
into 'web/'
include '**/*.html'
include '**/*.js'
include '**/*.jpg'
include '**/*.png'
}
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into "${projectDir}/web"
include { fileTreeElement ->
def path = fileTreeElement.path
path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
}
}
}
}
clean.doLast {
file(new File(projectDir, "/web")).deleteDir()
}
compileKotlin2Js {
kotlinOptions.outputFile = "${projectDir}/web/output.js"
kotlinOptions.moduleKind = "amd"
kotlinOptions.sourceMap = true
}
Firstly, you can assign a dynamic variable then essentially code it like you code JavaScript, dynamically.
e.g.
val jQuery: dynamic = passedInJQueryRef
jQuery.whateverFunc()
But if your intention is to have it typed, then you need to introduce types to the external library. One way is to make use of the relatively extensive libraries of typedefs by https://github.com/DefinitelyTyped/DefinitelyTyped
Find the ts.d there, then run ts2kt (https://github.com/Kotlin/ts2kt) to get your Kotlin files. That typically gets you there. Occasionally, certain conversions are not well done. You will have to hand fix the conversion. E.g. snapsvg's snapsvg.attr() call takes in "{}" but it got converted to some strange interface.
It was
fun attr(params: `ts$2`): Snap.Element
And I replaced it with
fun attr(params: Json): Snap.Element
and it works like a charm.
Kotlin 1.1 introduces the externalmodifier that can be used to declare functions and classes written directly in JS, see http://kotlinlang.org/docs/reference/js-interop.html