commonMain.aws.smithy.kotlin.runtime.serde.json.JsonDeserializer.kt Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.smithy.kotlin.runtime.serde.json
import aws.smithy.kotlin.runtime.InternalApi
import aws.smithy.kotlin.runtime.content.BigDecimal
import aws.smithy.kotlin.runtime.content.BigInteger
import aws.smithy.kotlin.runtime.content.Document
import aws.smithy.kotlin.runtime.serde.*
import aws.smithy.kotlin.runtime.text.encoding.decodeBase64Bytes
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.TimestampFormat
/**
* Provides a deserializer for JSON documents
*
* @param payload underlying document from which tokens are read
*/
@InternalApi
public class JsonDeserializer(payload: ByteArray) :
Deserializer,
Deserializer.ElementIterator,
Deserializer.EntryIterator,
PrimitiveDeserializer {
@InternalApi
public companion object {
private val validNumberStrings = setOf("Infinity", "-Infinity", "NaN")
}
private val reader = jsonStreamReader(payload)
// deserializing a single byte isn't common in JSON - we are going to assume that bytes are represented
// as numbers and user understands any truncation issues. `deserializeByte` is more common in binary
// formats (e.g. protobufs) where the binary encoding stores metadata in a single byte (e.g. flags or headers)
override fun deserializeByte(): Byte = nextNumberValue { it.toByteOrNull() ?: it.toDouble().toInt().toByte() }
override fun deserializeInt(): Int = nextNumberValue { it.toIntOrNull() ?: it.toDouble().toInt() }
override fun deserializeShort(): Short = nextNumberValue { it.toShortOrNull() ?: it.toDouble().toInt().toShort() }
override fun deserializeLong(): Long = nextNumberValue { it.toLongOrNull() ?: it.toDouble().toLong() }
override fun deserializeFloat(): Float = deserializeDouble().toFloat()
override fun deserializeDouble(): Double = nextNumberValue { it.toDouble() }
override fun deserializeBigInteger(): BigInteger = nextNumberValue(::BigInteger)
override fun deserializeBigDecimal(): BigDecimal = nextNumberValue(::BigDecimal)
// deserializes the next token as a number with the maximum discernible precision
private fun deserializeNumber(): Number =
nextNumberValue { if (it.contains('.')) it.toDouble() else it.toLong() }
// assert the next token is a Number and execute [block] with the raw value as a string. Returns result
// of executing the block. This is mostly so that numeric conversions can keep as much precision as possible
private fun nextNumberValue(block: (value: String) -> T): T {
val token = reader.nextToken()
return when {
token is JsonToken.Number -> block(token.value)
token is JsonToken.String && validNumberStrings.contains(token.value) -> block(token.value)
else -> throw DeserializationException("$token cannot be deserialized as type Number")
}
}
override fun deserializeString(): String =
// allow for tokens to be consumed as string even when the next token isn't a quoted string
when (val token = reader.nextToken()) {
is JsonToken.String -> token.value
is JsonToken.Number -> token.value
is JsonToken.Bool -> token.value.toString()
else -> throw DeserializationException("$token cannot be deserialized as type String")
}
override fun deserializeBoolean(): Boolean {
val token = reader.nextTokenOf()
return token.value
}
override fun deserializeDocument(): Document =
checkNotNull(deserializeDocumentImpl()) { "expected non-null document field" }
private fun deserializeDocumentImpl(): Document? =
when (val token = reader.peek()) {
is JsonToken.Number -> Document(deserializeNumber())
is JsonToken.String -> Document(deserializeString())
is JsonToken.Bool -> Document(deserializeBoolean())
JsonToken.Null -> {
reader.nextToken()
null
}
JsonToken.BeginArray ->
deserializeList(SdkFieldDescriptor(SerialKind.Document)) {
val values = mutableListOf()
while (hasNextElement()) {
values.add(deserializeDocumentImpl())
}
Document.List(values)
}
JsonToken.BeginObject ->
deserializeMap(SdkFieldDescriptor(SerialKind.Document)) {
val values = mutableMapOf()
while (hasNextEntry()) {
values[key()] = deserializeDocumentImpl()
}
Document.Map(values)
}
JsonToken.EndArray, JsonToken.EndObject, JsonToken.EndDocument ->
throw DeserializationException(
"encountered unexpected json token \"$token\" while deserializing document",
)
is JsonToken.Name ->
throw DeserializationException(
"encountered unexpected json field declaration \"${token.value}\" while deserializing document",
)
}
override fun deserializeNull(): Nothing? {
reader.nextTokenOf()
return null
}
override fun deserializeStruct(descriptor: SdkObjectDescriptor): Deserializer.FieldIterator =
when (reader.peek()) {
JsonToken.BeginObject -> {
reader.nextTokenOf()
JsonFieldIterator(reader, descriptor, this)
}
JsonToken.Null -> JsonNullFieldIterator(this)
else -> throw DeserializationException("Unexpected token type ${reader.peek()}")
}
override fun deserializeList(descriptor: SdkFieldDescriptor): Deserializer.ElementIterator {
reader.nextTokenOf()
return this
}
override fun deserializeMap(descriptor: SdkFieldDescriptor): Deserializer.EntryIterator {
reader.nextTokenOf()
return this
}
override fun key(): String {
val token = reader.nextTokenOf()
return token.value
}
override fun deserializeByteArray(): ByteArray = deserializeString().decodeBase64Bytes()
override fun deserializeInstant(format: TimestampFormat): Instant = when (format) {
TimestampFormat.EPOCH_SECONDS -> deserializeString().let { Instant.fromEpochSeconds(it) }
TimestampFormat.ISO_8601 -> deserializeString().let { Instant.fromIso8601(it) }
TimestampFormat.RFC_5322 -> deserializeString().let { Instant.fromRfc5322(it) }
else -> throw DeserializationException("unknown timestamp format: $format")
}
override fun nextHasValue(): Boolean = reader.peek() != JsonToken.Null
override fun hasNextEntry(): Boolean =
when (reader.peek()) {
JsonToken.EndObject -> {
// consume the token
reader.nextTokenOf()
false
}
JsonToken.Null,
JsonToken.EndDocument,
-> false
else -> true
}
override fun hasNextElement(): Boolean =
when (reader.peek()) {
JsonToken.EndArray -> {
// consume the token
reader.nextTokenOf()
false
}
JsonToken.EndDocument -> false
else -> true
}
}
// Represents the deserialization of a null object.
private class JsonNullFieldIterator(deserializer: JsonDeserializer) :
Deserializer.FieldIterator,
Deserializer by deserializer,
PrimitiveDeserializer by deserializer {
override fun findNextFieldIndex(): Int? = null
override fun skipValue(): Unit = throw DeserializationException("This should not be called during deserialization.")
}
private class JsonFieldIterator(
private val reader: JsonStreamReader,
private val descriptor: SdkObjectDescriptor,
deserializer: JsonDeserializer,
) : Deserializer.FieldIterator,
Deserializer by deserializer,
PrimitiveDeserializer by deserializer {
override fun findNextFieldIndex(): Int? {
val candidate = when (reader.peek()) {
JsonToken.EndObject -> {
// consume the token
reader.nextTokenOf()
null
}
JsonToken.EndDocument -> null
JsonToken.Null -> {
reader.nextTokenOf()
null
}
else -> {
val token = reader.nextTokenOf()
val propertyName = token.value
val field = descriptor.fields.find { it.serialName == propertyName }
if (IgnoreKey(propertyName) in descriptor.traits) {
reader.skipNext() // the value of the ignored key
return findNextFieldIndex()
} else {
field?.index ?: Deserializer.FieldIterator.UNKNOWN_FIELD
}
}
}
if (candidate != null) {
// found a field
if (reader.peek() == JsonToken.Null) {
// skip explicit nulls
reader.nextTokenOf()
return findNextFieldIndex()
}
}
return candidate
}
override fun skipValue() {
// stream reader skips the *next* token
reader.skipNext()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy