iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0

昨天我們看過了 ContentNegotiation 的實作。今天,我們來看看

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
}

裡面 json 的部分是怎麼實作的。

首先,當然是看看 json 函數

public fun Configuration.json(
    json: Json = DefaultJson,
    contentType: ContentType = ContentType.Application.Json
) {
    serialization(contentType, json)
}

這裡面預設使用了 DefaultJsonContentType.Application.Json

DefaultJson 的實作則是

public val DefaultJson: Json = Json {
    encodeDefaults = true
    isLenient = true
    allowSpecialFloatingPointValues = true
    allowStructuredMapKeys = true
    prettyPrint = false
    useArrayPolymorphism = false
}

這樣的設計,允許我們以自己定義的 Json 物件來替換預設的 parsing 方式

也就是可以像這樣使用 json 函數

install(ContentNegotiation) {
    json(Json {
        prettyPrint = true
        isLenient = true
    })
}

我們來看看 Json 的註解

/**
 * The main entry point to work with JSON serialization.
 * It is typically used by constructing an application-specific instance, with configured JSON-specific behaviour
 * and, if necessary, registered in [SerializersModule] custom serializers.
 * `Json` instance can be configured in its `Json {}` factory function using [JsonBuilder].
 * For demonstration purposes or trivial usages, Json [companion][Json.Default] can be used instead.
 *
 * Then constructed instance can be used either as regular [SerialFormat] or [StringFormat]
 * or for converting objects to [JsonElement] back and forth.
 *
 * This is the only serial format which has the first-class [JsonElement] support.
 * Any serializable class can be serialized to or from [JsonElement] with [Json.decodeFromJsonElement] and [Json.encodeToJsonElement] respectively or
 * serialize properties of [JsonElement] type.
 *
 * Example of usage:
 * ```
 * @Serializable
 * class DataHolder(val id: Int, val data: String, val extensions: JsonElement)
 *
 * val json = Json
 * val instance = DataHolder(42, "some data", buildJsonObject { put("additional key", "value") }
 *
 * // Plain StringFormat usage
 * val stringOutput: String = json.encodeToString(instance)
 *
 * // JsonElement serialization specific for JSON only
 * val jsonTree: JsonElement = json.encodeToJsonElement(instance)
 *
 * // Deserialize from string
 * val deserialized: DataHolder = json.decodeFromString<DataHolder>(stringOutput)
 *
 * // Deserialize from json tree, JSON-specific
 * val deserializedFromTree: DataHolder = json.decodeFromJsonElement<DataHolder>(jsonTree)
 *
 *  // Deserialize from string to JSON tree, JSON-specific
 *  val deserializedToTree: JsonElement = json.parseToJsonElement(stringOutput)
 * ```
 *
 * Json instance also exposes its [configuration] that can be used in custom serializers
 * that rely on [JsonDecoder] and [JsonEncoder] for customizable behaviour.
 */

如註解所說,這個物件就是 JSON 處理的主要物件了

接著我們來看看建立 Json 物件的 Json 函數

/**
 * Creates an instance of [Json] configured from the optionally given [Json instance][from] and adjusted with [builderAction].
 */
public fun Json(from: Json = Json.Default, builderAction: JsonBuilder.() -> Unit): Json {
    val builder = JsonBuilder(from)
    builder.builderAction()
    val conf = builder.build()
    return JsonImpl(conf, builder.serializersModule)
}

這邊利用 JsonBuilder.build() 可以建立出 JsonConfiguration 物件

@OptIn(ExperimentalSerializationApi::class)
internal fun build(): JsonConfiguration {
	if (useArrayPolymorphism) require(classDiscriminator == defaultDiscriminator) {
		"Class discriminator should not be specified when array polymorphism is specified"
	}

	if (!prettyPrint) {
		require(prettyPrintIndent == defaultIndent) {
			"Indent should not be specified when default printing mode is used"
		}
	} else if (prettyPrintIndent != defaultIndent) {
		// Values allowed by JSON specification as whitespaces
		val allWhitespaces = prettyPrintIndent.all { it == ' ' || it == '\t' || it == '\r' || it == '\n' }
		require(allWhitespaces) {
			"Only whitespace, tab, newline and carriage return are allowed as pretty print symbols. Had $prettyPrintIndent"
		}
	}

	return JsonConfiguration(
		encodeDefaults, ignoreUnknownKeys, isLenient,
		allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
		coerceInputValues, useArrayPolymorphism,
		classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
		namingStrategy
	)
}

接著,我們看看 ContentType.Application.Json 的實作

public val Json: ContentType = ContentType("application", "json")

這邊的 ContentType 是用來處理 HTML 的 Content-Type

/**
 * Represents a value for a `Content-Type` header.
 * @property contentType represents a type part of the media type.
 * @property contentSubtype represents a subtype part of the media type.
 */
 public class ContentType private constructor(
    public val contentType: String,
    public val contentSubtype: String,
    existingContent: String,
    parameters: List<HeaderValueParam> = emptyList()
) : HeaderValueWithParameters(existingContent, parameters) 

我們先不往下追蹤,到這邊概略理解邏輯即可。

繼續往下看 serialization(contentType, json)

/**
 * Register kotlinx.serialization converter into [ContentNegotiation] plugin
 * with the specified [contentType] and string [format] (such as Json)
 */
public fun Configuration.serialization(contentType: ContentType, format: StringFormat) {
    register(contentType, KotlinxSerializationConverter(format))
}

這邊會建立一個 KotlinxSerializationConverter

/**
 * Creates a converter serializing with the specified string [format]
 */
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
public class KotlinxSerializationConverter(
    private val format: SerialFormat,
) : ContentConverter 

可以看出,Ktor 是使用 KotlinxSerializationConverter 這個物件

來進行將收到的請求轉換成物件

或者是將處理後的物件轉換成 json 格式的回傳內容

到這邊,我們就看過 json() 所安裝的東西

以及發現到原來設定了許多 configuration 之後

最後是透過 KotlinxSerializationConverter 這個物件來進行轉換

明天我們就來看看對應的程式如何撰寫,以及對應的實作又是什麼!


上一篇
Day 12:處理 API 輸入輸出格式的 ContentNegotiation
下一篇
Day 14:call.receive 如何將請求轉換成自定義類別
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言