Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -155,27 +155,27 @@ class JsonCoerceInputValuesTest : JsonTestBase() {
fun testNullableEnumWithoutDefault() {
val j = Json(json) { explicitNulls = false }
parametrizedTest { mode ->
assertEquals(NullableEnumHolder(null), j.decodeFromString("{}"))
assertEquals(NullableEnumHolder(null), j.decodeFromString("""{"enum":"incorrect"}"""))
assertEquals(NullableEnumHolder(null), j.decodeFromString("{}", mode))
assertEquals(NullableEnumHolder(null), j.decodeFromString("""{"enum":"incorrect"}""", mode))
}
}

@Test
fun testNullableEnumWithoutDefaultDoesNotCoerceExplicitly() {
val j = Json(json) { explicitNulls = true }
parametrizedTest { mode ->
assertFailsWith<SerializationException> { j.decodeFromString<NullableEnumHolder>("{}") }
assertFailsWith<SerializationException> { j.decodeFromString<NullableEnumHolder>("""{"enum":"incorrect"}""") }
assertFailsWith<SerializationException> { j.decodeFromString<NullableEnumHolder>("{}", mode) }
assertFailsWith<SerializationException> { j.decodeFromString<NullableEnumHolder>("""{"enum":"incorrect"}""", mode) }
}
}

@Test
fun testNullableEnumWithDefault() {
val j = Json(json) { explicitNulls = false }
parametrizedTest { mode ->
assertEquals(NullableEnumWithDefault(), j.decodeFromString("{}"))
assertEquals(NullableEnumWithDefault(), j.decodeFromString("""{"e":"incorrect"}"""))
assertEquals(NullableEnumWithDefault(null), j.decodeFromString("""{"e":null}"""))
assertEquals(NullableEnumWithDefault(), j.decodeFromString("{}", mode))
assertEquals(NullableEnumWithDefault(), j.decodeFromString("""{"e":"incorrect"}""", mode))
assertEquals(NullableEnumWithDefault(null), j.decodeFromString("""{"e":null}""", mode))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class JsonCoerceInputValuesDynamicTest {
isLenient = true
}

private fun <T> doTest(inputs: List<dynamic>, expected: T, serializer: KSerializer<T>) {
private fun <T> doTest(inputs: List<dynamic>, expected: T, serializer: KSerializer<T>, jsonImpl: Json = json) {
for (input in inputs) {
assertEquals(expected, json.decodeFromDynamic(serializer, input), "Failed on input: $input")
assertEquals(expected, jsonImpl.decodeFromDynamic(serializer, input), "Failed on input: $input")
}
}

Expand Down Expand Up @@ -49,6 +49,20 @@ class JsonCoerceInputValuesDynamicTest {
}
}

@Test
fun testUseNullWithImplicitNulls() {
val withImplicitNulls = Json(json) { explicitNulls = false }
doTest(
listOf(
js("""{}"""),
js("""{"enum":"incorrect"}"""),
),
JsonCoerceInputValuesTest.NullableEnumHolder(null),
JsonCoerceInputValuesTest.NullableEnumHolder.serializer(),
withImplicitNulls
)
}

@Test
fun testUseDefaultInMultipleCases() {
val testData = mapOf<dynamic, JsonCoerceInputValuesTest.MultipleValues>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,31 +197,35 @@ private open class JsonTreeDecoder(
) : AbstractJsonTreeDecoder(json, value, polymorphicDiscriminator) {
private var position = 0
private var forceNull: Boolean = false
/*
* Checks whether JSON has `null` value for non-null property or unknown enum value for enum property
*/
private fun coerceInputValue(descriptor: SerialDescriptor, index: Int, tag: String): Boolean =
json.tryCoerceValue(
descriptor, index,
{ currentElement(tag) is JsonNull },
{ (currentElement(tag) as? JsonPrimitive)?.contentOrNull }
)

override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
while (position < descriptor.elementsCount) {
val name = descriptor.getTag(position++)
val index = position - 1
forceNull = false
if ((name in value || absenceIsNull(descriptor, index))
&& (!configuration.coerceInputValues || !coerceInputValue(descriptor, index, name))
) {

if (name in value || setForceNull(descriptor, index)) {
// if forceNull is true, then decodeNotNullMark returns false and `null` is automatically inserted
// by Decoder.decodeIfNullable
if (!configuration.coerceInputValues) return index

if (json.tryCoerceValue(
descriptor, index,
{ currentElementOrNull(name) is JsonNull },
{ (currentElementOrNull(name) as? JsonPrimitive)?.contentOrNull },
{ // an unknown enum value should be coerced to null via decodeNotNullMark if explicitNulls=false :
if (setForceNull(descriptor, index)) return index
}
)
) continue // do not read coerced value

return index
}
}
return CompositeDecoder.DECODE_DONE
}

private fun absenceIsNull(descriptor: SerialDescriptor, index: Int): Boolean {
private fun setForceNull(descriptor: SerialDescriptor, index: Int): Boolean {
forceNull = !json.configuration.explicitNulls
&& !descriptor.isElementOptional(index) && descriptor.getElementDescriptor(index).isNullable
return forceNull
Expand Down Expand Up @@ -257,6 +261,8 @@ private open class JsonTreeDecoder(

override fun currentElement(tag: String): JsonElement = value.getValue(tag)

fun currentElementOrNull(tag: String): JsonElement? = value[tag]

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
// polyDiscriminator needs to be preserved so the check for unknown keys
// in endStructure can filter polyDiscriminator out.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,22 @@ private open class DynamicInput(
val name = descriptor.getTag(currentPosition++)
val index = currentPosition - 1
forceNull = false
if ((hasName(name) || absenceIsNull(descriptor, index)) && (!json.configuration.coerceInputValues || !coerceInputValue(descriptor, index, name))) {

if (hasName(name) || setForceNull(descriptor, index)) {
// if forceNull is true, then decodeNotNullMark returns false and `null` is automatically inserted
// by Decoder.decodeIfNullable
if (!json.configuration.coerceInputValues) return index

if (json.tryCoerceValue(
descriptor, index,
{ getByTag(name) == null },
{ getByTag(name) as? String },
{ // an unknown enum value should be coerced to null via decodeNotNullMark if explicitNulls=false :
if (setForceNull(descriptor, index)) return index
}
)
) continue // do not read coerced value

return index
}
}
Expand All @@ -94,7 +109,7 @@ private open class DynamicInput(

private fun hasName(name: String) = value[name] !== undefined

private fun absenceIsNull(descriptor: SerialDescriptor, index: Int): Boolean {
private fun setForceNull(descriptor: SerialDescriptor, index: Int): Boolean {
forceNull = !json.configuration.explicitNulls
&& !descriptor.isElementOptional(index) && descriptor.getElementDescriptor(index).isNullable
return forceNull
Expand Down