티스토리 뷰

Swift

Swift ) Key decoding strategy

Zedd0202 2018. 4. 8. 13:44
반응형

안녕하세요 :) Zedd입니다.

JSONDecoder에  Key decoding strategy가 새로 생겼습니다~~~~



KeyDecodingStrategy는 enum으로, case가 3가지 있습니다.


useDefaultKeys

convertFromSnakeCase

custom(([CodingKey]) -> CodingKey)


이렇게 3가지가 있으며, Default는 useDefaultKeys입니다 :)


하나하나 볼까요?



● useDefaultKeys

디코딩 중에 키 이름을 변경하지 않는 Key decoding strategy입니다.

useDefaultKeys 전략은 지정하지 않으면 사용되는 전략입니다.


끝..


●  convertFromSnakeCase

드디어...드디어ㅓㅓㅓㅓㅓㅓㅓㅓㅓㅓㅓ드디어 나온부분


Snake-case(다들 아실거라 믿어요..)를 Camel-case로 변환하는 Key decoding strategy입니다.


Snake-case와 Camel-case는 API의 일부를 명명 할 때 단어를 결합하는 두 가지 일반적인 방법입니다. 

Swift API 디자인 가이드 라인에 의하면, Camel-case 이름을 사용하는 것이 좋습니다.


일부 JSON API는 Snake-case를 채택합니다. 그러한 API를 접할 때 이 전략(strategy)을 사용하십시오.

이 전략은 대문자 Letters와 lowercaseLetters를 사용하여 단어 사이의 경계를 결정하고 글자를 대문자로 표시 할 때 시스템 locale을 결정합니다.


이 전략은 JSON 키를 camel-case로 변환하기위한 다음 단계를 따릅니다.


1. 밑줄(_) 뒤에 오는 각 단어를 대문자로합니다.

2. 문자열의 맨 처음이나 끝에 있지 않은 모든 밑줄을 제거합니다.

3. 단어를 하나의 문자열로 결합합니다.


fee_fi_fo_fum

Converts to: feeFiFoFum

feeFiFoFum

Converts to: feeFiFoFum

base_uri

Converts to: baseUri



말로만 하지말고, 예제로 봅시다 ㄱ



struct OlympicEventResult: Codable {

    var goldWinner: String

    var silverWinner: String

    var bronzeWinner: String

}



OlympicEventResult라는 구조체를 하나 만들게요.

그리고, 이제 json을 하나 만들어야 하는데...

let json = """

{

"silver_winner": "Sound",

"gold_winner": "Light",

"bronze_winner": "Unladen Swallow"

}

"""


Snake-case로 만들겠습니다!!!!!!



let decoder = JSONDecoder()

let data = json.data(using: .utf8)


if let data = data, let result = try? decoder.decode(OlympicEventResult.self, from: data) {

            print(result.goldWinner)

            print(result.silverWinner)

            print(result.bronzeWinner)

}


이렇게 하면 결과가 어떻게 될까요?


..아무것도 print되지 않습니다! 왜냐면 아직 strategy를 안줬음 ㅎ

JSONDecoder는 Key이름과 프로퍼티이름이 일치하는 곳에 value를 할당하는데,

key이름은 gold_winner이지만..프로퍼티에는 goldWinner는 있고 gold_winner는 없으니..

저 try?구문에서 실패를 하고 넘어가게 되죠.

하지만...! convertFromSnakeCase를 사용해봅시다.


let decoder = JSONDecoder()

let data = json.data(using: .utf8)


decoder.keyDecodingStrategy = .convertFromSnakeCase


if let data = data, let result = try? decoder.decode(OlympicEventResult.self, from: data) {

            print(result.goldWinner)

            print(result.silverWinner)

            print(result.bronzeWinner)

}



이렇게 Key decoding strategy로 convertFromSnakeCase를 지정해주면..!



이렇게 아주 잘 나오는 것을 볼 수 있습니다 XD..


와 그럼 <왕초보를 위한 Codable - Codingkey>글에서 봤듯이;;; 이제 enum으로 막 CodingKey준수하고 그런 작업 안해줘도 돼?

=> 는 아닙니다.. convertFromSnakeCase가 작동하는 단계가


1. 밑줄(_) 뒤에 오는 각 단어를 대문자로합니다.

2. 문자열의 맨 처음이나 끝에 있지 않은 모든 밑줄을 제거합니다.

3. 단어를 하나의 문자열로 결합합니다.


이렇다고 그랬죠?



만약 이렇게 key와 프로퍼티 이름이, _를 제외하고 “다르다면”

여전히 위 방법으로 enum을 만들어서 해야겠죠.

아무튼 그래도 convertFromSnakeCase가 나와서 넘나 좋은것..


아직 안끝났습니다..!

하나 더 남았어요 :)


● custom(([CodingKey]) -> CodingKey)

정의는 당신이 제공하는 클로저에 의해 정의 된 key decoding strategy.


이 case와 관련된 값은(The value associated with this case) 디코딩된 JSON객체의 key이름을 타입의 CodingKey이름에 매핑하는데 사용되는 클로저 입니다.

디코딩 하는 동안, 디코드 되는 Decodable 값에서, 각 Key에 대해 클로저가 한번 호출됩니다.

클로져는, 디코드 되는 값에 도달하기 위헤서 필요한 일련의 키를 나타내는 CodingKey인스턴스의 배열과 함께 불립니다.


아래 예제는 custom(([CodingKey]) -> CodingKey)을 사용하여 중첩된 A,B 및 C구조의 특성을 디코딩 하는 방법을 보여줍니다.


struct A: Codable {

    var value: Int

    var b: B

    

    struct B: Codable {

        var value: Int

        var c: C

        

        struct C: Codable {

            var value: Int

        }

    }

}



오 넘나 복잡한 구조체인것 nested type으로 정의를 했네요.



let json = """

{

    "a.value": 1,

    "b": {

        "a.b.value": 2,

        "c": {

            "a.b.c.value": 3

        }

    }

}

""".data(using: .utf8)!


JSON도 역시...엄청난(?) 중첩구조를 가지고 있습니다. 이런거 Codable로 하려면 어케함 ㅡㅡ

ㄱㄷㄱㄷ


struct AnyKey: CodingKey {

    var stringValue: String

    var intValue: Int?

    

    init?(stringValue: String) {

        self.stringValue = stringValue

        self.intValue = nil

    }

    

    init?(intValue: Int) {

        self.stringValue = String(intValue)

        self.intValue = intValue

    }

}



갑자기 CodingKey프로토콜을 채택하는 AnyKey라는 구조체를 정의해줬습니다.

CodingKey라는 프로토콜이 요구하는 뭔가가 있나보네요.




          let decoder = JSONDecoder()

            decoder.keyDecodingStrategy = .custom { keys in

            let lastComponent = keys.last!.stringValue.split(separator: ".").last!

            return AnyKey(stringValue: String(lastComponent))!

        }

        

        let a = try? decoder.decode(A.self, from: json)

        if let a = a {

            print(a.b.c.value) // Prints "3"

        }


자, 클로저 안을 주목해주세요. 

저 클로저는 각 Key에 대해 한번 호출된다고 했으니..


let json = """

{

    "a.value": 1,

    "b": {

        "a.b.value": 2,

        "c": {

            "a.b.c.value": 3

        }

    }

}

""".data(using: .utf8)!


위 JSON에서 Key라고 말할거는 5개가 있죠? 한줄씩..

그럼 5번이 호출될 것 같은 느낌



그러하다 ㅇㅇ

여기서 왜인지는 모르지만, 배열의 가장 마지막에는 JSONKey가 들어가나본데, 우리는 이 JSONKey만 사용해야합니다.


           decoder.keyDecodingStrategy = .custom { keys in

            let lastComponent = keys.last!.stringValue.split(separator: ".").last!

            return AnyKey(stringValue: String(lastComponent))!

        }


keys(배열)의 마지막 원소의 stringValue(엥 last에 stringValue라는 프로퍼티가 있음? -> 이 keys는 CodingKey프로토콜 타입 배열이므로 stringValue가 있음 ㅇㅇ)

를 가져와서. 그거를 .을 기준으로 나눈뒤에 split은 배열을 리턴하므로, 또 그 배열의 last를 AnyKey의 이니셜라이저 파라미터로 넣어줍니다.



let a = try? decoder.decode(A.self, from: json)

        if let a = a {

            print(a.b.c.value) // Prints "3"

        }


이렇게 하면..저렇게 복잡한 중첩구조의 JSON도 잘 풀 수 있나보네요.

솔직히 이 내부적인?..구조가 잘 이해가 안가는데...ㅎㅎ


좀 실전에서 쓰면서 알아봐야 할 것 같아요 :)


아무튼 저는 convertFromSnakeCase가 나와줘서 아주 감사한...Apple....Love..

솔직히 지금 Codable을 사용하고 있지는 않지만, 이 convertFromSnakeCase덕분에 나중에 편해질 수도 있으니까요 :)


아무튼 Codable을 사용하시는 분들게 도움이 되는 글이길 바래요 XD


출처 : https://developer.apple.com/documentation/foundation/jsondecoder.keydecodingstrategy?changes=latest_minor

반응형

'Swift' 카테고리의 다른 글

Swift 4.1 Released! -2  (0) 2018.04.14
Swift ) Hashable  (1) 2018.04.10
Swift 4.1 Released! - 1  (0) 2018.04.07
Swift ) NSString.CompareOptions종류  (0) 2018.03.24
Swift ) ComparisonResult살펴보기  (0) 2018.03.24