JSON ์ฒ˜๋ฆฌ

Davidยท2022๋…„ 12์›” 15์ผ
0

[Anroid] Network

๋ชฉ๋ก ๋ณด๊ธฐ
2/2
post-thumbnail

๐Ÿค” ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ JSON ์ฒ˜๋ฆฌ


๊ธฐ์กด ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ๋Š” json ์ฒ˜๋ฆฌ์—
gson ๊ณผ Moshi ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

๋‚˜๋Š” ์•„์ง๊นŒ์ง€ gson์ด ํŽธํ•˜๊ณ  ๋ฐ”๊ฟ”์•ผ ํ•  ํ•„์š”์„ฑ์€ ๋Š๋ผ์ง€ ๋ชป ํ–ˆ์—ˆ๋Š”๋ฐ
์ตœ๊ทผ Compose๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ
๊ตฌ๊ธ€ ํ”„๋กœ์ ํŠธ์—์„œ json ์ฒ˜๋ฆฌ๋ฅผ
Moshi -> kotlinx-serialization๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์ถ”์„ธ๋ฅผ ๋ณด๊ณ 
๊ด€์‹ฌ์ด ์ƒ๊ฒจ ์ •๋ฆฌํ•ด๋ณผ๋ ค๊ณ  ํ•œ๋‹ค.


๐Ÿ“Œ kotlinx-serialization


๐Ÿ’ก ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

โœ… reflective mode ์ฝ”๋“œ ์ƒ์„ฑ์„ ํ†ตํ•ด Gson ๋ฐ Moshi ๋ณด๋‹ค ๋น ๋ฅธ ๋Ÿฐํƒ€์ž„ ์„ฑ๋Šฅ
โœ… annotation ํ”„๋กœ์„ธ์„œ๊ฐ€ ์•„๋‹Œ ์ปดํŒŒ์ผ๋Ÿฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•˜์—ฌ Moshi๋ณด๋‹ค ๋น ๋ฅธ ๋นŒ๋“œ ์‹œ๊ฐ„
โœ… ์ฝ”ํ‹€๋ฆฐ์˜ ์œ ํ˜• ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ์ง€์› ํ–ฅ์ƒ ๋ฐ ๋ฌดํšจ๊ฐ’๊ณผ ๊ธฐ๋ณธ๊ฐ’์— ๋Œ€ํ•œ ์ง€์›
โœ… ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์ธ์ฝ”๋”ฉ ํ˜•์‹ ์ง€์›


๐ŸŒฑ ์‚ฌ์šฉ๋ฒ•

๐Ÿ‘‰๐Ÿป ์•ˆ๋“œ๋กœ์ด๋“œ codelab - retrofit2 with kotlinx.serialization
๐Ÿ‘‰๐Ÿป kotlinx.serialization
๐Ÿ‘‰ ์˜ˆ์‹œ์ฝ”๋“œ github

1. json์œผ๋กœ ์—ญ*์ง๋ ฌํ™” ํ•  ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค ์ƒ์„ฑ @Serializable

import kotlinx.serialization.Serializable

@Serializable
data class Person(
    val name: String,
    val age: Int = 31,
    val sex: Char = 'M',
    val hobby: String = "Game of League of Legends"
)

2. JsonBuilder ์˜ต์…˜ ์„ค์ •

val json = Json {
    encodeDefaults = true // encode ์‹œ ๋””ํดํŠธ ์„ค์ • ๊ฐ’์ด ํฌํ•จ๋˜์„œ ์ธ์ฝ”๋”ฉ ๋จ
    ignoreUnknownKeys = true // ์ฝ”ํ‹€๋ฆฐ ํด๋ž˜์Šค๋กœ ๋ณ€ํ™˜ ์‹œ json ์—†๋Š” ํ”„๋กœํผํ‹ฐ๋Š” ๋ฌด์‹œ 
}
public class JsonBuilder internal constructor(json: Json) {
    /**
     * Specifies whether default values of Kotlin properties should be encoded.
     * `false` by default.
     */
    public var encodeDefaults: Boolean = json.configuration.encodeDefaults

    /**
     * Specifies whether `null` values should be encoded for nullable properties and must be present in JSON object
     * during decoding.
     *
     * When this flag is disabled properties with `null` values without default are not encoded;
     * during decoding, the absence of a field value is treated as `null` for nullable properties without a default value.
     *
     * `true` by default.
     */
    @ExperimentalSerializationApi
    public var explicitNulls: Boolean = json.configuration.explicitNulls

    /**
     * Specifies whether encounters of unknown properties in the input JSON
     * should be ignored instead of throwing [SerializationException].
     * `false` by default.
     */
    public var ignoreUnknownKeys: Boolean = json.configuration.ignoreUnknownKeys

    /**
     * Removes JSON specification restriction (RFC-4627) and makes parser
     * more liberal to the malformed input. In lenient mode quoted boolean literals,
     * and unquoted string literals are allowed.
     *
     * Its relaxations can be expanded in the future, so that lenient parser becomes even more
     * permissive to invalid value in the input, replacing them with defaults.
     *
     * `false` by default.
     */
    public var isLenient: Boolean = json.configuration.isLenient

    /**
     * Enables structured objects to be serialized as map keys by
     * changing serialized form of the map from JSON object (key-value pairs) to flat array like `[k1, v1, k2, v2]`.
     * `false` by default.
     */
    public var allowStructuredMapKeys: Boolean = json.configuration.allowStructuredMapKeys

    /**
     * Specifies whether resulting JSON should be pretty-printed.
     *  `false` by default.
     */
    public var prettyPrint: Boolean = json.configuration.prettyPrint

    /**
     * Specifies indent string to use with [prettyPrint] mode
     * 4 spaces by default.
     * Experimentality note: this API is experimental because
     * it is not clear whether this option has compelling use-cases.
     */
    @ExperimentalSerializationApi
    public var prettyPrintIndent: String = json.configuration.prettyPrintIndent

    /**
     * Enables coercing incorrect JSON values to the default property value in the following cases:
     *   1. JSON value is `null` but property type is non-nullable.
     *   2. Property type is an enum type, but JSON value contains unknown enum member.
     *
     * `false` by default.
     */
    public var coerceInputValues: Boolean = json.configuration.coerceInputValues

    /**
     * Switches polymorphic serialization to the default array format.
     * This is an option for legacy JSON format and should not be generally used.
     * `false` by default.
     */
    public var useArrayPolymorphism: Boolean = json.configuration.useArrayPolymorphism

    /**
     * Name of the class descriptor property for polymorphic serialization.
     * "type" by default.
     */
    public var classDiscriminator: String = json.configuration.classDiscriminator

    /**
     * Removes JSON specification restriction on
     * special floating-point values such as `NaN` and `Infinity` and enables their serialization and deserialization.
     * When enabling it, please ensure that the receiving party will be able to encode and decode these special values.
     * `false` by default.
     */
    public var allowSpecialFloatingPointValues: Boolean = json.configuration.allowSpecialFloatingPointValues

    /**
     * Specifies whether Json instance makes use of [JsonNames] annotation.
     *
     * Disabling this flag when one does not use [JsonNames] at all may sometimes result in better performance,
     * particularly when a large count of fields is skipped with [ignoreUnknownKeys].
     * `true` by default.
     */
    public var useAlternativeNames: Boolean = json.configuration.useAlternativeNames

    /**
     * Module with contextual and polymorphic serializers to be used in the resulting [Json] instance.
     */
    public var serializersModule: SerializersModule = json.serializersModule

3. ์ธ์ฝ”๋“œ ๋””์ฝ”๋“œ

// kClass -> json (encode)
private fun encodePersonToJson(json: Json) {
    val person = Person(
        name = "david"
    )
    println("=======================Encode=======================")
    println("person: $person")
    println("Encode process")
    println("json: ${json.encodeToString(person)}")
    println("====================================================")
}

// ๊ฒฐ๊ณผ
=======================Encode=======================
person: Person(name=david, age=31, sex=M, hobby=Game of League of Legends)
Encode process
json: {"name":"david","age":31,"sex":"M","hobby":"Game of League of Legends"}
====================================================
// json -> kClass (decode)
private fun decodeJsonToPerson(json: Json) {
    val jsonPerson = """
        {
            "name" : "david"
            "age" : 32
            "address" : "incheon" // Person์— ์—†๋Š” ํ”„๋กœํผํ‹ฐ
        }
    """
    val person = json.decodeFromString<Person>(jsonPerson)
    println("=======================Decode=======================")
    println("json: $jsonPerson")
    println("Decode process")
    println("person: $person")
    println("====================================================")
}

// ๊ฒฐ๊ณผ
=======================Decode=======================
json: 
        {
            "name" : "david"
            "age" : 32
            "address" : "incheon"
        }
    
Decode process
person: Person(name=david, age=32, sex=M, hobby=Game of League of Legends) ๐Ÿ‘ˆ๐Ÿป "address" ํ”„๋กœํผํ‹ฐ ๋ฌด์‹œ
====================================================

โšก๏ธ sealed class์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ

0. ๋‹ค์–‘ํ•˜๊ฒŒ ๋‚ด๋ ค์˜ค๋Š” ํƒ€์ž…์˜ json

1. ๋‹ค์–‘ํ•œ ํƒ€์ž…์„ ํด๋ž˜์Šค๋กœ ๋‚˜๋ˆ„๊ธฐ ์œ„ํ•ด sealed class ์ƒ์„ฑ

package advanced

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
sealed class ViewType

@Serializable
@SerialName("vt001") // ๊ตฌ๋ถ„ ๋ช…์‹œ์ž๋ฅผ ์•Œ๋ ค์คŒ
data class VT001(
    val title: String,
    val data: List<String>
): ViewType()

@Serializable
@SerialName("vt002") // ๊ตฌ๋ถ„ ๋ช…์‹œ์ž๋ฅผ ์•Œ๋ ค์คŒ
data class VT002(
    val title: String,
    val keyword: String
): ViewType()


@Serializable
@SerialName("vt003") // ๊ตฌ๋ถ„ ๋ช…์‹œ์ž๋ฅผ ์•Œ๋ ค์คŒ
data class VT003(
    val title: String,
    val data: List<String>,
    val views: Int
): ViewType()

2. JsonBuilder ์˜ต์…˜ ์„ค์ • > classDiscriminator

val json = Json {
   ...
   classDiscriminator = "viewType" // viewType์œผ๋กœ ๊ตฌ๋ถ„
}

3. Decode ํ•˜๊ธฐ

expectJsonDecodeLog<VT003>(json) {
        """
            {
                "viewType" : "vt003",
                "title" : "์ธ๊ธฐ ์ฝ˜ํ…์ธ ",
                "data" : [
                    "์—ฐ์• ์ฐธ๊ฒฌ", "์žฌ๋ฒŒ์ง‘ ๋ง‰๋‚ด ์•„๋“ค", "์ข…์ด์˜ ์ง‘"
                ],
                "views" : 200
            } 
        """
}

// ๊ฒฐ๊ณผ
expect type[VT003] 
JsonDecode type[VT003] 
JsonDecode classInfo > VT003(title=์ธ๊ธฐ ์ฝ˜ํ…์ธ , data=[์—ฐ์• ์ฐธ๊ฒฌ, ์žฌ๋ฒŒ์ง‘ ๋ง‰๋‚ด ์•„๋“ค, ์ข…์ด์˜ ์ง‘], views=200) ๐Ÿ‘ˆ๐Ÿป "VT003 ํด๋ž˜์Šค๋กœ ๋ณ€ํ™˜๋จ"

๐Ÿ‘‹๐Ÿป ํฌ์ŠคํŠธ๋ฅผ ๋งˆ์น˜๋ฉฐ


  • ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ์ข‹์€ ์ 
  • ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•
  • sealed class ์‚ฌ์šฉ๋ฒ•

๊ณต์œ ํ•˜๋ฉฐ ๋Š๋‚€ ์ ์€ ๋‹ค์–‘ํ•œ ํƒ€์ž…์„ ์ง€์›ํ•˜๋ฉด์„œ
์ฝ”ํ‹€๋ฆฐ์„ ์‚ฌ์šฉํ•  ๋•Œ ๊ธฐ๋ณธ๊ฐ’๊ณผ ์„œ๋ฒ„์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ณผ ๊ท€์ฐฎ์€ ๊ฒƒ๋“ค์ด
์ƒ๋‹นํžˆ ํŽธ๋ฆฌํ•˜๊ฒŒ ๋Š๊ปด์กŒ์Šต๋‹ˆ๋‹ค.

๊ฐœ๋ฐœํ•˜๋ฉฐ ์œ„์˜ ๋‚ด์šฉ์—์„œ ์กฐ๊ธˆ ๋” ์ถ”๊ฐ€์ ์œผ๋กœ ๊ณต์œ  ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

profile
๊ณต๋ถ€ํ•˜๋Š” ๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€