code
package site.daydream.colleen.json
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.assertThrows
import java.io.InputStream
import java.util.stream.Stream
import kotlin.reflect.javaType
import kotlin.reflect.typeOf
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
// ========================================================================
// Test Data Classes
// ========================================================================
data class SimpleUser(
val id: Int,
val name: String
)
data class UserWithNullable(
val id: Int,
val name: String,
val email: String? = null,
val age: Int? = null
)
data class UserWithDefaults(
val id: Int,
val name: String = "Unknown",
val active: Boolean = true,
val score: Double = 0.0
)
data class NestedUser(
val id: Int,
val name: String,
val address: Address?
)
data class Address(
val street: String,
val city: String,
val zipCode: String? = null
)
data class GenericContainer<T>(
val value: T,
val metadata: Map<String, String>? = null
)
data class ComplexGeneric<K, V>(
val data: Map<K, List<V>>,
val count: Int
)
// ========================================================================
// Helper Functions - Combining TypeRef and JsonMapper
// ========================================================================
// === Deserialization (JSON String -> Object) ===
/**
* Deserialize using reified type (inline function with typeOf)
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Any> fromJson(mapper: JsonMapper, data: String): T {
return mapper.fromJsonString(data, typeOf<T>().javaType)
}
/**
* Deserialize using TypeRef
*/
fun <T : Any> fromJson(mapper: JsonMapper, data: String, typeRef: TypeRef<T>): T {
return mapper.fromJsonString(data, typeRef.type)
}
/**
* Deserialize using Class
*/
fun <T : Any> fromJson(mapper: JsonMapper, data: String, clazz: Class<T>): T {
return mapper.fromJsonString(data, clazz)
}
/**
* Deserialize from InputStream using reified type
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Any> fromJsonStream(mapper: JsonMapper, stream: InputStream): T {
return mapper.fromJsonStream(stream, typeOf<T>().javaType)
}
/**
* Deserialize from InputStream using TypeRef
*/
fun <T : Any> fromJsonStream(mapper: JsonMapper, stream: InputStream, typeRef: TypeRef<T>): T {
return mapper.fromJsonStream(stream, typeRef.type)
}
// === Serialization (Object -> JSON String) ===
/**
* Serialize using reified type
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Any> toJson(mapper: JsonMapper, obj: T): String {
return mapper.toJsonString(obj, typeOf<T>().javaType)
}
/**
* Serialize using TypeRef
*/
fun <T : Any> toJson(mapper: JsonMapper, obj: T, typeRef: TypeRef<T>): String {
return mapper.toJsonString(obj, typeRef.type)
}
/**
* Serialize to InputStream using reified type
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Any> toJsonStream(mapper: JsonMapper, obj: T): InputStream {
return mapper.toJsonStream(obj, typeOf<T>().javaType)
}
/**
* Serialize to InputStream using TypeRef
*/
fun <T : Any> toJsonStream(mapper: JsonMapper, obj: T, typeRef: TypeRef<T>): InputStream {
return mapper.toJsonStream(obj, typeRef.type)
}
// === ConvertValue ===
/**
* Convert value using reified type
*/
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Any> convertValue(mapper: JsonMapper, obj: Any): T {
return mapper.convertValue(obj, typeOf<T>().javaType)
}
/**
* Convert value using TypeRef
*/
fun <T : Any> convertValue(mapper: JsonMapper, obj: Any, typeRef: TypeRef<T>): T {
return mapper.convertValue(obj, typeRef.type)
}
// ========================================================================
// Combined TypeRef and JsonMapper Tests
// ========================================================================
class TypeRefJsonMapperIntegrationTest {
private lateinit var mapper: JsonMapper
@BeforeEach
fun setup() {
mapper = ColleenJackson()
}
// ========================================================================
// Reified Type Tests (inline + typeOf)
// ========================================================================
@Nested
inner class ReifiedTypeTests {
@Test
fun `should deserialize simple object using reified type`() {
// Arrange
val json = """{"id":1,"name":"Alice"}"""
// Act
val user = fromJson<SimpleUser>(mapper, json)
// Assert
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@Test
fun `should deserialize List using reified type`() {
// Arrange
val json = """[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]"""
// Act
val users = fromJson<List<SimpleUser>>(mapper, json)
// Assert
assertEquals(2, users.size)
assertEquals("Alice", users[0].name)
assertEquals("Bob", users[1].name)
}
@Test
fun `should deserialize Map using reified type`() {
// Arrange
val json = """{"user1":{"id":1,"name":"Alice"},"user2":{"id":2,"name":"Bob"}}"""
// Act
val userMap = fromJson<Map<String, SimpleUser>>(mapper, json)
// Assert
assertEquals(2, userMap.size)
assertEquals("Alice", userMap["user1"]?.name)
assertEquals("Bob", userMap["user2"]?.name)
}
@Test
fun `should deserialize nested generics using reified type`() {
// Arrange
val json = """{"value":{"id":1,"name":"Alice"},"metadata":{"key":"value"}}"""
// Act
val container = fromJson<GenericContainer<SimpleUser>>(mapper, json)
// Assert
assertEquals(1, container.value.id)
assertEquals("Alice", container.value.name)
assertEquals("value", container.metadata?.get("key"))
}
@Test
fun `should serialize and deserialize using reified type`() {
// Arrange
val original = SimpleUser(1, "Alice")
// Act
val json = toJson(mapper, original)
val deserialized = fromJson<SimpleUser>(mapper, json)
// Assert
assertEquals(original, deserialized)
}
@Test
fun `should handle nullable fields with reified type`() {
// Arrange
val json = """{"id":1,"name":"Bob","email":"bob@example.com"}"""
// Act
val user = fromJson<UserWithNullable>(mapper, json)
// Assert
assertEquals(1, user.id)
assertEquals("bob@example.com", user.email)
assertEquals(null, user.age)
}
@Test
fun `should handle default values with reified type`() {
// Arrange
val json = """{"id":1}"""
// Act
val user = fromJson<UserWithDefaults>(mapper, json)
// Assert
assertEquals(1, user.id)
assertEquals("Unknown", user.name)
assertEquals(true, user.active)
}
}
// ========================================================================
// TypeRef Tests (anonymous object syntax)
// ========================================================================
@Nested
inner class TypeRefTests {
@Test
fun `should deserialize simple object using TypeRef`() {
// Arrange
val json = """{"id":1,"name":"Alice"}"""
val typeRef = TypeRef.of(SimpleUser::class.java)
// Act
val user = fromJson(mapper, json, typeRef)
// Assert
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@Test
fun `should deserialize List using TypeRef factory method`() {
// Arrange
val json = """[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]"""
val typeRef = TypeRef.listOf(SimpleUser::class.java)
// Act
val users = fromJson(mapper, json, typeRef)
// Assert
assertEquals(2, users.size)
assertEquals("Alice", users[0].name)
assertEquals("Bob", users[1].name)
}
@Test
fun `should deserialize Set using TypeRef factory method`() {
// Arrange
val json = """["Alice","Bob","Charlie"]"""
val typeRef = TypeRef.setOf(String::class.java)
// Act
val names = fromJson(mapper, json, typeRef)
// Assert
assertEquals(3, names.size)
assertTrue(names.contains("Alice"))
assertTrue(names.contains("Bob"))
}
@Test
fun `should deserialize Map using TypeRef factory method`() {
// Arrange
val json = """{"user1":{"id":1,"name":"Alice"},"user2":{"id":2,"name":"Bob"}}"""
val typeRef = TypeRef.mapOf(String::class.java, SimpleUser::class.java)
// Act
val userMap = fromJson(mapper, json, typeRef)
// Assert
assertEquals(2, userMap.size)
assertEquals("Alice", userMap["user1"]?.name)
assertEquals("Bob", userMap["user2"]?.name)
}
@Test
fun `should deserialize nested List using anonymous TypeRef`() {
// Arrange
val json = """[["a","b"],["c","d"],["e","f"]]"""
val typeRef = object : TypeRef<List<List<String>>>() {}
// Act
val nestedList = fromJson(mapper, json, typeRef)
// Assert
assertEquals(3, nestedList.size)
assertEquals(2, nestedList[0].size)
assertEquals("a", nestedList[0][0])
assertEquals("f", nestedList[2][1])
}
@Test
fun `should deserialize Map with List values using anonymous TypeRef`() {
// Arrange
val json =
"""{"team1":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}],"team2":[{"id":3,"name":"Charlie"}]}"""
val typeRef = object : TypeRef<Map<String, List<SimpleUser>>>() {}
// Act
val teams = fromJson(mapper, json, typeRef)
// Assert
assertEquals(2, teams.size)
assertEquals(2, teams["team1"]?.size)
assertEquals("Alice", teams["team1"]?.get(0)?.name)
assertEquals(1, teams["team2"]?.size)
}
@Test
fun `should deserialize generic container using anonymous TypeRef`() {
// Arrange
val json = """{"value":{"id":1,"name":"Alice"},"metadata":{"version":"1.0"}}"""
val typeRef = object : TypeRef<GenericContainer<SimpleUser>>() {}
// Act
val container = fromJson(mapper, json, typeRef)
// Assert
assertEquals(1, container.value.id)
assertEquals("Alice", container.value.name)
assertEquals("1.0", container.metadata?.get("version"))
}
@Test
fun `should deserialize complex nested generic using anonymous TypeRef`() {
// Arrange
val json = """{"data":{"group1":[{"id":1,"name":"Alice"}],"group2":[{"id":2,"name":"Bob"}]},"count":2}"""
val typeRef = object : TypeRef<ComplexGeneric<String, SimpleUser>>() {}
// Act
val complex = fromJson(mapper, json, typeRef)
// Assert
assertEquals(2, complex.count)
assertEquals(1, complex.data["group1"]?.size)
assertEquals("Alice", complex.data["group1"]?.get(0)?.name)
}
@Test
fun `should deserialize List of Maps using anonymous TypeRef`() {
// Arrange
val json = """[{"key1":"value1"},{"key2":"value2"}]"""
val typeRef = object : TypeRef<List<Map<String, String>>>() {}
// Act
val listOfMaps = fromJson(mapper, json, typeRef)
// Assert
assertEquals(2, listOfMaps.size)
assertEquals("value1", listOfMaps[0]["key1"])
assertEquals("value2", listOfMaps[1]["key2"])
}
@Test
fun `should deserialize Map of Lists of Maps using anonymous TypeRef`() {
// Arrange
val json = """{"group1":[{"id":1},{"id":2}],"group2":[{"id":3}]}"""
val typeRef = object : TypeRef<Map<String, List<Map<String, Int>>>>() {}
// Act
val complexMap = fromJson(mapper, json, typeRef)
// Assert
assertEquals(2, complexMap.size)
assertEquals(2, complexMap["group1"]?.size)
assertEquals(1, complexMap["group1"]?.get(0)?.get("id"))
}
}
// ========================================================================
// Class-based Tests
// ========================================================================
@Nested
inner class ClassBasedTests {
@Test
fun `should deserialize using Class parameter`() {
// Arrange
val json = """{"id":1,"name":"Alice"}"""
// Act
val user = fromJson(mapper, json, SimpleUser::class.java)
// Assert
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@Test
fun `should serialize and deserialize using Class parameter`() {
// Arrange
val original = SimpleUser(1, "Alice")
val json = toJson(mapper, original)
// Act
val deserialized = fromJson(mapper, json, SimpleUser::class.java)
// Assert
assertEquals(original, deserialized)
}
}
// ========================================================================
// Stream Tests with TypeRef
// ========================================================================
@Nested
inner class StreamTests {
@Test
fun `should deserialize from InputStream using reified type`() {
// Arrange
val json = """{"id":1,"name":"Alice"}"""
val stream = json.byteInputStream()
// Act
val user = fromJsonStream<SimpleUser>(mapper, stream)
// Assert
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@Test
fun `should deserialize from InputStream using TypeRef`() {
// Arrange
val json = """[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]"""
val stream = json.byteInputStream()
val typeRef = TypeRef.listOf(SimpleUser::class.java)
// Act
val users = fromJsonStream(mapper, stream, typeRef)
// Assert
assertEquals(2, users.size)
assertEquals("Alice", users[0].name)
}
@Test
fun `should serialize to InputStream and deserialize using reified type`() {
// Arrange
val original = SimpleUser(1, "Alice")
// Act
val stream = toJsonStream(mapper, original)
val deserialized = fromJsonStream<SimpleUser>(mapper, stream)
// Assert
assertEquals(original, deserialized)
}
@Test
fun `should handle complex generics through stream using reified type`() {
// Arrange
val original = GenericContainer(
value = SimpleUser(1, "Alice"),
metadata = mapOf("version" to "1.0")
)
// Act
val stream = toJsonStream(mapper, original)
val deserialized = fromJsonStream<GenericContainer<SimpleUser>>(mapper, stream)
// Assert
assertEquals(original, deserialized)
}
}
// ========================================================================
// Comparison Tests - Different Type Specification Methods
// ========================================================================
@Nested
inner class ComparisonTests {
@Test
fun `all three methods should produce same result for simple type`() {
// Arrange
val json = """{"id":1,"name":"Alice"}"""
// Act
val result1 = fromJson<SimpleUser>(mapper, json)
val result2 = fromJson(mapper, json, TypeRef.of(SimpleUser::class.java))
val result3 = fromJson(mapper, json, SimpleUser::class.java)
// Assert
assertEquals(result1, result2)
assertEquals(result2, result3)
}
@Test
fun `reified and TypeRef should produce same result for List`() {
// Arrange
val json = """[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]"""
// Act
val result1 = fromJson<List<SimpleUser>>(mapper, json)
val result2 = fromJson(mapper, json, TypeRef.listOf(SimpleUser::class.java))
// Assert
assertEquals(result1, result2)
}
@Test
fun `reified and TypeRef should produce same result for Map`() {
// Arrange
val json = """{"user1":{"id":1,"name":"Alice"}}"""
// Act
val result1 = fromJson<Map<String, SimpleUser>>(mapper, json)
val result2 = fromJson(mapper, json, TypeRef.mapOf(String::class.java, SimpleUser::class.java))
// Assert
assertEquals(result1, result2)
}
@Test
fun `reified and anonymous TypeRef should produce same result for nested generics`() {
// Arrange
val json = """{"value":{"id":1,"name":"Alice"},"metadata":{"key":"value"}}"""
// Act
val result1 = fromJson<GenericContainer<SimpleUser>>(mapper, json)
val result2 = fromJson(mapper, json, object : TypeRef<GenericContainer<SimpleUser>>() {})
// Assert
assertEquals(result1, result2)
}
}
// ========================================================================
// ConvertValue Tests with TypeRef
// ========================================================================
@Nested
inner class ConvertValueTests {
@Test
fun `should convert Map to object using reified type`() {
// Arrange
val map = mapOf("id" to 1, "name" to "Alice")
// Act
val user = convertValue<SimpleUser>(mapper, map)
// Assert
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@Test
fun `should convert Map to object using TypeRef`() {
// Arrange
val map = mapOf("id" to 1, "name" to "Alice")
val typeRef = TypeRef.of(SimpleUser::class.java)
// Act
val user = convertValue(mapper, map, typeRef)
// Assert
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@Test
fun `should convert object to Map using reified type`() {
// Arrange
val user = SimpleUser(1, "Alice")
// Act
val map = convertValue<Map<String, Any>>(mapper, user)
// Assert
assertEquals(1, map["id"])
assertEquals("Alice", map["name"])
}
@Test
fun `should convert List of Maps to List of objects using reified type`() {
// Arrange
val maps = listOf(
mapOf("id" to 1, "name" to "Alice"),
mapOf("id" to 2, "name" to "Bob")
)
// Act
val users = convertValue<List<SimpleUser>>(mapper, maps)
// Assert
assertEquals(2, users.size)
assertEquals("Alice", users[0].name)
assertEquals("Bob", users[1].name)
}
@Test
fun `should convert List of Maps to List of objects using TypeRef`() {
// Arrange
val maps = listOf(
mapOf("id" to 1, "name" to "Alice"),
mapOf("id" to 2, "name" to "Bob")
)
val typeRef = TypeRef.listOf(SimpleUser::class.java)
// Act
val users = convertValue(mapper, maps, typeRef)
// Assert
assertEquals(2, users.size)
assertEquals("Alice", users[0].name)
}
@Test
fun `should convert nested Map to nested object using anonymous TypeRef`() {
// Arrange
val map = mapOf(
"id" to 1,
"name" to "Charlie",
"address" to mapOf(
"street" to "123 Main St",
"city" to "Springfield",
"zipCode" to "12345"
)
)
val typeRef = TypeRef.of(NestedUser::class.java)
// Act
val user = convertValue(mapper, map, typeRef)
// Assert
assertEquals(1, user.id)
assertNotNull(user.address)
assertEquals("123 Main St", user.address?.street)
}
@Test
fun `should convert Map with generic container using anonymous TypeRef`() {
// Arrange
val map = mapOf(
"value" to mapOf("id" to 1, "name" to "Alice"),
"metadata" to mapOf("version" to "1.0")
)
val typeRef = object : TypeRef<GenericContainer<SimpleUser>>() {}
// Act
val container = convertValue(mapper, map, typeRef)
// Assert
assertEquals(1, container.value.id)
assertEquals("1.0", container.metadata?.get("version"))
}
@Test
fun `should convert complex nested structures using anonymous TypeRef`() {
// Arrange
val map = mapOf(
"team1" to listOf(
mapOf("id" to 1, "name" to "Alice"),
mapOf("id" to 2, "name" to "Bob")
),
"team2" to listOf(
mapOf("id" to 3, "name" to "Charlie")
)
)
val typeRef = object : TypeRef<Map<String, List<SimpleUser>>>() {}
// Act
val teams = convertValue(mapper, map, typeRef)
// Assert
assertEquals(2, teams.size)
assertEquals(2, teams["team1"]?.size)
assertEquals("Alice", teams["team1"]?.get(0)?.name)
}
@Test
fun `should handle nullable fields in conversion using reified type`() {
// Arrange
val map = mapOf("id" to 1, "name" to "Bob", "email" to "bob@example.com")
// Act
val user = convertValue<UserWithNullable>(mapper, map)
// Assert
assertEquals("bob@example.com", user.email)
assertEquals(null, user.age)
}
@Test
fun `should convert between different collection types using TypeRef`() {
// Arrange
val list = listOf("Alice", "Bob", "Charlie")
val typeRef = TypeRef.setOf(String::class.java)
// Act
val set = convertValue(mapper, list, typeRef)
// Assert
assertEquals(3, set.size)
assertTrue(set.contains("Alice"))
}
@Test
fun `should chain convertValue operations with different TypeRefs`() {
// Arrange
val user = SimpleUser(1, "Alice")
// Act - Convert to Map then back to object
val map = convertValue<Map<String, Any>>(mapper, user)
val typeRef = TypeRef.of(SimpleUser::class.java)
val converted = convertValue(mapper, map, typeRef)
// Assert
assertEquals(user, converted)
}
}
// ========================================================================
// WriteToOutputStream Tests with TypeRef
// ========================================================================
@Nested
inner class WriteToOutputStreamTests {
@Test
fun `should write Stream of simple objects to OutputStream`() {
// Arrange
val users = Stream.of(
SimpleUser(1, "Alice"),
SimpleUser(2, "Bob"),
SimpleUser(3, "Charlie")
)
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(users, outputStream)
// Assert
val json = outputStream.toString()
assertTrue(json.startsWith("["))
assertTrue(json.endsWith("]"))
assertTrue(json.contains("\"name\":\"Alice\""))
assertTrue(json.contains("\"name\":\"Bob\""))
assertTrue(json.contains("\"name\":\"Charlie\""))
}
@Test
fun `should write Stream of objects with nullable fields to OutputStream`() {
// Arrange
val users = Stream.of(
UserWithNullable(1, "Alice", "alice@example.com", null),
UserWithNullable(2, "Bob", null, 25)
)
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(users, outputStream)
// Assert
val json = outputStream.toString()
assertTrue(json.contains("\"email\":\"alice@example.com\""))
assertTrue(json.contains("\"age\":25"))
}
@Test
fun `should write Stream of nested objects to OutputStream`() {
// Arrange
val users = Stream.of(
NestedUser(1, "Alice", Address("123 Main St", "Springfield", "12345")),
NestedUser(2, "Bob", null)
)
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(users, outputStream)
// Assert
val json = outputStream.toString()
assertTrue(json.contains("\"address\""))
assertTrue(json.contains("\"street\":\"123 Main St\""))
}
@Test
fun `should write empty Stream to OutputStream`() {
// Arrange
val users = Stream.empty<SimpleUser>()
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(users, outputStream)
// Assert
val json = outputStream.toString()
assertEquals("[]", json)
}
@Test
fun `should write single element Stream to OutputStream`() {
// Arrange
val users = Stream.of(SimpleUser(1, "Alice"))
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(users, outputStream)
// Assert
val json = outputStream.toString()
assertTrue(json.startsWith("["))
assertTrue(json.contains("\"name\":\"Alice\""))
}
@Test
fun `should write Stream of generic containers to OutputStream`() {
// Arrange
val containers = Stream.of(
GenericContainer(SimpleUser(1, "Alice"), mapOf("version" to "1.0")),
GenericContainer(SimpleUser(2, "Bob"), mapOf("version" to "2.0"))
)
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(containers, outputStream)
// Assert
val json = outputStream.toString()
assertTrue(json.contains("\"version\":\"1.0\""))
assertTrue(json.contains("\"version\":\"2.0\""))
}
@Test
fun `should write Stream and then deserialize result using TypeRef`() {
// Arrange
val original = listOf(
SimpleUser(1, "Alice"),
SimpleUser(2, "Bob")
)
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(original.stream(), outputStream)
val json = outputStream.toString()
val typeRef = TypeRef.listOf(SimpleUser::class.java)
val deserialized = fromJson(mapper, json, typeRef)
// Assert
assertEquals(original, deserialized)
}
@Test
fun `should handle large Stream efficiently`() {
// Arrange
val largeStream = (1..1000).map { SimpleUser(it, "User$it") }.stream()
val outputStream = java.io.ByteArrayOutputStream()
// Act
mapper.writeToOutputStream(largeStream, outputStream)
// Assert
val json = outputStream.toString()
assertTrue(json.contains("\"name\":\"User1\""))
assertTrue(json.contains("\"name\":\"User1000\""))
val typeRef = TypeRef.listOf(SimpleUser::class.java)
val deserialized = fromJson(mapper, json, typeRef)
assertEquals(1000, deserialized.size)
}
}
// ========================================================================
// Serialization Methods Comparison Tests
// ========================================================================
@Nested
inner class SerializationMethodsComparisonTests {
@Test
fun `toJsonString with reified type should match with TypeRef`() {
// Arrange
val user = SimpleUser(1, "Alice")
val typeRef = object : TypeRef<SimpleUser>() {}
// Act
val json1 = toJson(mapper, user)
val json2 = toJson(mapper, user, typeRef)
// Assert
assertEquals(json1, json2)
}
@Test
fun `toJsonStream with reified type should match with TypeRef`() {
// Arrange
val user = SimpleUser(1, "Alice")
val typeRef = object : TypeRef<SimpleUser>() {}
// Act
val stream1 = toJsonStream(mapper, user)
val stream2 = toJsonStream(mapper, user, typeRef)
val json1 = stream1.bufferedReader().use { it.readText() }
val json2 = stream2.bufferedReader().use { it.readText() }
// Assert
assertEquals(json1, json2)
}
@Test
fun `all serialization methods should produce deserializable JSON`() {
// Arrange
val original = listOf(SimpleUser(1, "Alice"), SimpleUser(2, "Bob"))
// Act - Serialize using different methods
val json1 = toJson(mapper, original)
val stream = toJsonStream(mapper, original)
val json2 = stream.bufferedReader().use { it.readText() }
// Deserialize both
val typeRef = TypeRef.listOf(SimpleUser::class.java)
val result1 = fromJson(mapper, json1, typeRef)
val result2 = fromJson(mapper, json2, typeRef)
// Assert
assertEquals(original, result1)
assertEquals(original, result2)
}
}
// ========================================================================
// Edge Cases with TypeRef
// ========================================================================
@Nested
inner class EdgeCasesWithTypeRef {
@Test
fun `should handle empty List with TypeRef`() {
// Arrange
val json = """[]"""
val typeRef = TypeRef.listOf(SimpleUser::class.java)
// Act
val users = fromJson(mapper, json, typeRef)
// Assert
assertTrue(users.isEmpty())
}
@Test
fun `should handle empty Map with TypeRef`() {
// Arrange
val json = """{}"""
val typeRef = TypeRef.mapOf(String::class.java, SimpleUser::class.java)
// Act
val userMap = fromJson(mapper, json, typeRef)
// Assert
assertTrue(userMap.isEmpty())
}
@Test
fun `should handle nullable nested object with TypeRef`() {
// Arrange
val json = """{"id":1,"name":"Alice","address":null}"""
val typeRef = TypeRef.of(NestedUser::class.java)
// Act
val user = fromJson(mapper, json, typeRef)
// Assert
assertEquals(1, user.id)
assertEquals(null, user.address)
}
@Test
fun `should handle List with nullable elements`() {
// Arrange
val json = """[{"id":1,"name":"Alice","email":null}]"""
val typeRef = TypeRef.listOf(UserWithNullable::class.java)
// Act
val users = fromJson(mapper, json, typeRef)
// Assert
assertEquals(1, users.size)
assertEquals(null, users[0].email)
}
@Test
fun `should handle String input directly in toJsonString`() {
// Arrange
val jsonString = """{"id":1,"name":"Test"}"""
// Act
val result = toJson(mapper, jsonString)
// Assert
assertEquals(jsonString, result, "String should be returned as-is")
}
@Test
fun `should handle String input directly in toJsonStream`() {
// Arrange
val jsonString = """{"id":1,"name":"Test"}"""
// Act
val stream = toJsonStream(mapper, jsonString)
val result = stream.bufferedReader().use { it.readText() }
// Assert
assertEquals(jsonString, result, "String should be returned as-is in stream")
}
@Test
fun `should serialize and deserialize object with defaults using all methods`() {
// Arrange
val original = UserWithDefaults(id = 1, name = "Custom", active = false)
// Act & Assert - toJsonString + fromJsonString
val json1 = toJson(mapper, original)
val result1 = fromJson<UserWithDefaults>(mapper, json1)
assertEquals(original, result1)
// Act & Assert - toJsonStream + fromJsonStream
val stream = toJsonStream(mapper, original)
val result2 = fromJsonStream<UserWithDefaults>(mapper, stream)
assertEquals(original, result2)
// Act & Assert - convertValue
val map = convertValue<Map<String, Any>>(mapper, original)
val result3 = convertValue<UserWithDefaults>(mapper, map)
assertEquals(original, result3)
}
}
// ========================================================================
// Type-Inference Serialization Tests (without explicit type specification)
// ========================================================================
@Nested
inner class TypeInferenceSerializationTests {
@Test
fun `should serialize simple object without type specification`() {
// Arrange
val user = SimpleUser(1, "Alice")
// Act - Use object's actual class
val json = mapper.toJsonString(user, user.javaClass)
// Assert
assertTrue(json.contains("\"id\":1"))
assertTrue(json.contains("\"name\":\"Alice\""))
}
@Test
fun `should serialize List without type specification`() {
// Arrange
val users = listOf(
SimpleUser(1, "Alice"),
SimpleUser(2, "Bob")
)
// Act - Use actual runtime class
val json = mapper.toJsonString(users, users.javaClass)
// Assert
assertTrue(json.startsWith("["))
assertTrue(json.contains("\"name\":\"Alice\""))
assertTrue(json.contains("\"name\":\"Bob\""))
}
@Test
fun `should serialize Map without type specification`() {
// Arrange
val userMap = mapOf(
"user1" to SimpleUser(1, "Alice"),
"user2" to SimpleUser(2, "Bob")
)
// Act
val json = mapper.toJsonString(userMap, userMap.javaClass)
// Assert
assertTrue(json.contains("\"user1\""))
assertTrue(json.contains("\"name\":\"Alice\""))
}
@Test
fun `should serialize nested object without type specification`() {
// Arrange
val user = NestedUser(1, "Charlie", Address("123 Main St", "Springfield", "12345"))
// Act
val json = mapper.toJsonString(user, user.javaClass)
// Assert
assertTrue(json.contains("\"address\""))
assertTrue(json.contains("\"street\":\"123 Main St\""))
}
@Test
fun `should serialize generic container without explicit type parameter`() {
// Arrange
val container = GenericContainer(
value = SimpleUser(1, "Alice"),
metadata = mapOf("version" to "1.0")
)
// Act - Runtime type erasure, but should still work
val json = mapper.toJsonString(container, container.javaClass)
// Assert
assertTrue(json.contains("\"value\""))
assertTrue(json.contains("\"name\":\"Alice\""))
assertTrue(json.contains("\"metadata\""))
}
@Test
fun `should serialize to stream without type specification`() {
// Arrange
val user = SimpleUser(1, "Alice")
// Act
val stream = mapper.toJsonStream(user, user.javaClass)
val json = stream.bufferedReader().use { it.readText() }
// Assert
assertTrue(json.contains("\"name\":\"Alice\""))
}
@Test
fun `should handle null value serialization - returns JSON null`() {
// Arrange
val data: Any? = null
val value = data ?: "null" // value becomes the String "null"
// Act
val json = mapper.toJsonString(value, value.javaClass)
// Assert
// String "null" is treated as already-JSON by ColleenJackson (see toJsonString implementation)
assertEquals("null", json) // The literal string "null" is returned as-is, not quoted
}
@Test
fun `should handle actual null value serialization`() {
// Arrange - What if we want to serialize actual null?
val data: Any? = null
// Act
val json = if (data != null) {
mapper.toJsonString(data, data.javaClass)
} else {
"null" // Return JSON null directly
}
// Assert
assertEquals("null", json)
}
@Test
fun `should compare null handling strategies`() {
// Strategy 1: Use "null" string (current implementation)
val data1: Any? = null
val value1 = data1 ?: "null"
val json1 = mapper.toJsonString(value1, value1.javaClass)
assertEquals("null", json1) // String is passed through
// Strategy 2: Serialize actual null
val data2: Any? = null
val value2 = data2 ?: mapOf<String, Any?>() // Use empty object instead
val json2 = mapper.toJsonString(value2, value2.javaClass)
assertEquals("{}", json2)
// Strategy 3: Direct null JSON string
val json3 = "null"
assertEquals("null", json3)
}
@Test
fun `should serialize primitive wrapper types without type specification`() {
// Arrange
val number: Any = 42
val text: Any = "Hello"
val flag: Any = true
// Act
val jsonNumber = mapper.toJsonString(number, number.javaClass)
val jsonText = mapper.toJsonString(text, text.javaClass)
val jsonFlag = mapper.toJsonString(flag, flag.javaClass)
// Assert
assertEquals("42", jsonNumber)
// String is treated as already-JSON by ColleenJackson, so no quotes added
assertEquals("Hello", jsonText)
assertEquals("true", jsonFlag)
}
@Test
fun `should differentiate between JSON string and plain string`() {
// Arrange
val plainString = "Hello"
val jsonString = """{"message":"Hello"}"""
// Act
val result1 = mapper.toJsonString(plainString, plainString.javaClass)
val result2 = mapper.toJsonString(jsonString, jsonString.javaClass)
// Assert
// Both are treated as already-JSON by ColleenJackson
assertEquals("Hello", result1)
assertEquals("""{"message":"Hello"}""", result2)
}
@Test
fun `should serialize non-string primitives correctly`() {
// Arrange
val user = SimpleUser(42, "Test")
// Act - When we serialize an actual object
val json = mapper.toJsonString(user, user.javaClass)
// Assert - This is properly JSON-encoded
assertTrue(json.contains("\"name\":\"Test\"")) // String IS quoted in object context
assertTrue(json.contains("\"id\":42")) // Number is not quoted
}
@Test
fun `should serialize object with nullable fields without type specification`() {
// Arrange
val user = UserWithNullable(1, "Bob", "bob@example.com", null)
// Act
val json = mapper.toJsonString(user, user.javaClass)
// Assert
assertTrue(json.contains("\"email\":\"bob@example.com\""))
assertTrue(!json.contains("\"age\"")) // null fields omitted
}
@Test
fun `should round-trip serialize and deserialize without explicit types`() {
// Arrange
val original = SimpleUser(1, "Alice")
// Act - Serialize without type specification
val json = mapper.toJsonString(original, original.javaClass)
// Deserialize with TypeRef to verify
val deserialized = fromJson<SimpleUser>(mapper, json)
// Assert
assertEquals(original, deserialized)
}
@Test
fun `should handle complex collection types without type specification`() {
// Arrange
val data = mapOf(
"users" to listOf(SimpleUser(1, "Alice"), SimpleUser(2, "Bob")),
"count" to 2,
"active" to true
)
// Act
val json = mapper.toJsonString(data, data.javaClass)
// Assert
assertTrue(json.contains("\"users\""))
assertTrue(json.contains("\"count\":2"))
assertTrue(json.contains("\"active\":true"))
}
@Test
fun `should serialize empty collections without type specification`() {
// Arrange
val emptyList = emptyList<SimpleUser>()
val emptyMap = emptyMap<String, Any>()
// Act
val jsonList = mapper.toJsonString(emptyList, emptyList.javaClass)
val jsonMap = mapper.toJsonString(emptyMap, emptyMap.javaClass)
// Assert
assertEquals("[]", jsonList)
assertEquals("{}", jsonMap)
}
@Test
fun `should demonstrate ResponseBody Json class scenario - with null handling`() {
// Scenario 1: data is null
val data1: Any? = null
val value1 = data1 ?: "null" // becomes String "null"
val json1 = mapper.toJsonString(value1, value1.javaClass)
assertEquals("null", json1) // String "null" is treated as already-JSON
// Scenario 2: data is an object
val data2: Any? = SimpleUser(1, "Alice")
val value2 = data2 ?: "null"
val json2 = mapper.toJsonString(value2, value2.javaClass)
assertTrue(json2.contains("\"name\":\"Alice\""))
// Scenario 3: data is already a JSON string
val data3: Any? = """{"status":"ok"}"""
val value3 = data3 ?: "null"
val json3 = mapper.toJsonString(value3, value3.javaClass)
assertEquals("""{"status":"ok"}""", json3) // Passed through as-is
}
@Test
fun `should demonstrate ResponseBody Json class scenario - stream mode`() {
// Arrange - Simulate the ResponseBody.Json scenario
val data: Any? = listOf(SimpleUser(1, "Alice"), SimpleUser(2, "Bob"))
val value = data ?: "null"
// Act - Stream mode
val stream = mapper.toJsonStream(value, value.javaClass)
val json = stream.bufferedReader().use { it.readText() }
// Assert
assertTrue(json.startsWith("["))
assertTrue(json.contains("\"name\":\"Alice\""))
}
@Test
fun `should demonstrate ResponseBody Json class scenario - bytes mode`() {
// Arrange - Simulate the ResponseBody.Json scenario
val data: Any? = SimpleUser(1, "Alice")
val value = data ?: "null"
// Act - Bytes mode
val json = mapper.toJsonString(value, value.javaClass)
val bytes = json.toByteArray(Charsets.UTF_8)
// Assert
assertNotNull(bytes)
assertTrue(bytes.isNotEmpty())
val result = String(bytes, Charsets.UTF_8)
assertTrue(result.contains("\"name\":\"Alice\""))
}
@Test
fun `should handle polymorphic collections without type specification`() {
// Arrange - Mixed types in collection
val mixedList: List<Any> = listOf(
SimpleUser(1, "Alice"),
mapOf("key" to "value"),
"plain string",
42
)
// Act
val json = mapper.toJsonString(mixedList, mixedList.javaClass)
// Assert
assertTrue(json.startsWith("["))
assertTrue(json.contains("\"name\":\"Alice\""))
assertTrue(json.contains("\"key\":\"value\""))
assertTrue(json.contains("\"plain string\""))
assertTrue(json.contains("42"))
}
}
// ========================================================================
// Error Handling with TypeRef
// ========================================================================
@Nested
inner class ErrorHandlingWithTypeRef {
@Test
fun `should throw exception for invalid JSON with reified type`() {
// Arrange
val invalidJson = """{"id":1,"name":}"""
// Act & Assert
assertThrows<Exception> {
fromJson<SimpleUser>(mapper, invalidJson)
}
}
@Test
fun `should throw exception for invalid JSON with TypeRef`() {
// Arrange
val invalidJson = """{"id":1,"name":}"""
val typeRef = TypeRef.of(SimpleUser::class.java)
// Act & Assert
assertThrows<Exception> {
fromJson(mapper, invalidJson, typeRef)
}
}
@Test
fun `should throw exception for type mismatch with TypeRef`() {
// Arrange
val json = """{"id":"not_a_number","name":"Alice"}"""
val typeRef = TypeRef.of(SimpleUser::class.java)
// Act & Assert
assertThrows<Exception> {
fromJson(mapper, json, typeRef)
}
}
}
}