Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
alias(libs.plugins.ksp)
id("maven-publish")
Expand All @@ -30,6 +31,7 @@ kotlin {
dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization)
}
}
val commonTest by getting {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.adk.kt

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.adk.kt.serialization.Json
import kotlin.test.assertEquals
import kotlinx.serialization.json.JsonPrimitive
import org.junit.Test
import org.junit.runner.RunWith

/**
* Android behavior of the non-`@Serializable` fallback in `Json.serializeToJsonElement` (org.json).
*/
@RunWith(AndroidJUnit4::class)
class JsonSerializationFallbackAndroidTest {

// Not `@Serializable`, so the platform fallback (org.json on Android) is used.
private data class Unannotated(val v: Int)

@Test
fun nonSerializableType_stringifiesViaOrgJson() {
// org.json cannot reflect, so the value is stringified via `toString()`.
assertEquals(JsonPrimitive("Unannotated(v=7)"), Json.serializeToJsonElement(Unannotated(7)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

package com.google.adk.kt.serialization

import kotlin.reflect.KClass
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.serializerOrNull

/** Platform-independent utility for JSON serialization. */
interface Json {
/** Serializes an object to a JSON string. */
Expand All @@ -24,7 +29,23 @@ interface Json {
/** Parses a JSON string to a map. */
fun fromJsonToMap(json: String): Map<String, Any?>

companion object : Json by getJson()
companion object : Json by getJson() {
private val KMP_JSON = kotlinx.serialization.json.Json { explicitNulls = false }

/**
* Converts [value] to a [JsonElement]: kotlinx.serialization for `@Serializable` types,
* otherwise the platform [Json] fallback (best-effort, not round-trippable).
*/
@OptIn(InternalSerializationApi::class)
internal fun serializeToJsonElement(value: Any): JsonElement {
@Suppress("UNCHECKED_CAST") val serializer = (value::class as KClass<Any>).serializerOrNull()
return if (serializer != null) {
KMP_JSON.encodeToJsonElement(serializer, value)
} else {
KMP_JSON.parseToJsonElement(toJsonString(value))
}
}
}
}

internal expect fun getJson(): Json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.adk.kt.serialization

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json as KotlinxJson

class SerializeToJsonElementTest {

@Serializable private data class Point(val x: Int, val y: Int, val label: String? = null)

@Test
fun serializableType_emitsJsonObject() {
// `@Serializable` -> structured JSON object via kotlinx.serialization (JVM and Android).
assertEquals(
KotlinxJson.parseToJsonElement("""{"x":1,"y":2}"""),
Json.serializeToJsonElement(Point(1, 2)),
)
}

@Test
fun serializableType_omitsNullProperties() {
// `explicitNulls = false`: a null property (`label`) is dropped from the encoded object.
assertEquals(
KotlinxJson.parseToJsonElement("""{"x":1,"y":2}"""),
Json.serializeToJsonElement(Point(1, 2, label = null)),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.adk.kt

import com.google.adk.kt.serialization.Json
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.serialization.json.Json as KotlinxJson

/** JVM behavior of the non-`@Serializable` fallback in `Json.serializeToJsonElement` (Gson). */
class JsonSerializationFallbackJvmTest {

// Not `@Serializable`, so the platform fallback (Gson on JVM) is used.
private data class Unannotated(val v: Int)

@Test
fun nonSerializableType_reflectsToJsonObjectViaGson() {
// On JVM the Gson-backed fallback reflects the data class into a structured JSON object.
assertEquals(
KotlinxJson.parseToJsonElement("""{"v":7}"""),
Json.serializeToJsonElement(Unannotated(7)),
)
}
}
Loading