programing

Swift에서 Enum Decodable을 만들려면 어떻게 해야 합니까?

megabox 2023. 5. 14. 10:31
반응형

Swift에서 Enum Decodable을 만들려면 어떻게 해야 합니까?

enum PostType: Decodable {

    init(from decoder: Decoder) throws {

        // What do i put here?
    }

    case Image
    enum CodingKeys: String, CodingKey {
        case image
    }
}

이걸 완성하려면 어떻게 해야 하나요?또한, 제가 변경했다고 가정해 보겠습니다.case대상:

case image(value: Int)

디코딩할 수 있도록 하려면 어떻게 해야 합니까?

(작동하지 않는) 전체 코드입니다.

let jsonData = """
{
    "count": 4
}
""".data(using: .utf8)!
        
        do {
            let decoder = JSONDecoder()
            let response = try decoder.decode(PostType.self, from: jsonData)
            
            print(response)
        } catch {
            print(error)
        }
    }
}

enum PostType: Int, Codable {
    case count = 4
}

또한 이와 같은 열거형을 어떻게 처리할 것입니까?

enum PostType: Decodable {
    case count(number: Int)
}

그것은 꽤 쉽습니다, 그냥 사용하세요.String또는Int암시적으로 할당된 원시 값.

enum PostType: Int, Codable {
    case image, blob
}

image는 인딩됨에로 됩니다.0그리고.blob1

또는

enum PostType: String, Codable {
    case image, blob
}

image는 인딩됨에로 됩니다."image"그리고.blob"blob"


다음은 사용 방법의 간단한 예입니다.

enum PostType : Int, Codable {
    case count = 4
}

struct Post : Codable {
    var type : PostType
}

let jsonString = "{\"type\": 4}"

let jsonData = Data(jsonString.utf8)

do {
    let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
    print("decoded:", decoded.type)
} catch {
    print(error)
}

갱신하다

iOS 13.3+ 및 macOS 15.1+에서는 컬렉션 유형에 포함되지 않은 단일 JSON 값인 fragment를 인코딩/디코딩할 수 있습니다.

let jsonString = "4"

let jsonData = Data(jsonString.utf8)
do {
    let decoded = try JSONDecoder().decode(PostType.self, from: jsonData)
    print("decoded:", decoded) // -> decoded: count
} catch {
    print(error)
}

Swift 5.5+에서는 추가 코드 없이 관련 값으로 열거형을 인코딩/디코딩할 수도 있습니다.값이 사전에 매핑되고 관련된 각 값에 대해 매개 변수 레이블을 지정해야 합니다.

enum Rotation: Codable {
    case zAxis(angle: Double, speed: Int)
}

let jsonString = #"{"zAxis":{"angle":90,"speed":5}}"#

let jsonData = Data(jsonString.utf8)
do {
    let decoded = try JSONDecoder().decode(Rotation.self, from: jsonData)
    print("decoded:", decoded)
} catch {
    print(error)
}

된 유형의 은 있는열다을음방같거형법만이드는과관련형유이▁to▁how방법▁with▁toums▁en▁make▁conform는.Codable

은 @ @Howard Lovatt @Howard Lovatt의 답변을 만드는 것을 .PostTypeCodableForm 구화하사니다합용을 합니다.KeyedEncodingContainerApple에서 속성으로 제공한 유형Encoder그리고.Decoder보일러 플레이트를 줄여줍니다.

enum PostType: Codable {
    case count(number: Int)
    case title(String)
}

extension PostType {

    private enum CodingKeys: String, CodingKey {
        case count
        case title
    }

    enum PostTypeCodingError: Error {
        case decoding(String)
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if let value = try? values.decode(Int.self, forKey: .count) {
            self = .count(number: value)
            return
        }
        if let value = try? values.decode(String.self, forKey: .title) {
            self = .title(value)
            return
        }
        throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .count(let number):
            try container.encode(number, forKey: .count)
        case .title(let value):
            try container.encode(value, forKey: .title)
        }
    }
}

이 코드는 Xcode 9b3에서 작동합니다.

import Foundation // Needed for JSONEncoder/JSONDecoder

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()

let count = PostType.count(number: 42)
let countData = try encoder.encode(count)
let countJSON = String.init(data: countData, encoding: .utf8)!
print(countJSON)
//    {
//      "count" : 42
//    }

let decodedCount = try decoder.decode(PostType.self, from: countData)

let title = PostType.title("Hello, World!")
let titleData = try encoder.encode(title)
let titleJSON = String.init(data: titleData, encoding: .utf8)!
print(titleJSON)
//    {
//        "title": "Hello, World!"
//    }
let decodedTitle = try decoder.decode(PostType.self, from: titleData)

스위프트는 던질 것입니다..dataCorrupted알 수 없는 열거값이 발견되면 오류가 발생합니다.데이터가 서버에서 전송되는 경우 언제든지 알 수 없는 열거형 값을 보낼 수 있습니다(버그 서버 측, API 버전에 추가된 새로운 유형, 이전 버전의 앱이 사례를 정상적으로 처리하기를 원하는 경우 등). 준비를 하고 "방어 스타일"을 코드화하여 열거형을 안전하게 디코딩하는 것이 좋습니다.

다음은 관련 값을 포함하거나 포함하지 않는 방법에 대한 예입니다.

    enum MediaType: Decodable {
       case audio
       case multipleChoice
       case other
       // case other(String) -> we could also parametrise the enum like that

       init(from decoder: Decoder) throws {
          let label = try decoder.singleValueContainer().decode(String.self)
          switch label {
             case "AUDIO": self = .audio
             case "MULTIPLE_CHOICES": self = .multipleChoice
             default: self = .other
             // default: self = .other(label)
          }
       }
    }

그리고 그것을 둘러싸는 구조에서 사용하는 방법:

    struct Question {
       [...]
       let type: MediaType

       enum CodingKeys: String, CodingKey {
          [...]
          case type = "type"
       }


   extension Question: Decodable {
      init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         [...]
         type = try container.decode(MediaType.self, forKey: .type)
      }
   }

@@Toka에 원시 하여 @Toka 에하기응, 당을한가추을, 의▁a 없이 ▁without를 수 .switch:

enum MediaType: String, Decodable {
  case audio = "AUDIO"
  case multipleChoice = "MULTIPLE_CHOICES"
  case other

  init(from decoder: Decoder) throws {
    let label = try decoder.singleValueContainer().decode(String.self)
    self = MediaType(rawValue: label) ?? .other
  }
}

생성자를 리팩터링할 수 있는 사용자 정의 프로토콜을 사용하여 확장할 수 있습니다.

protocol EnumDecodable: RawRepresentable, Decodable {
  static var defaultDecoderValue: Self { get }
}

extension EnumDecodable where RawValue: Decodable {
  init(from decoder: Decoder) throws {
    let value = try decoder.singleValueContainer().decode(RawValue.self)
    self = Self(rawValue: value) ?? Self.defaultDecoderValue
  }
}

enum MediaType: String, EnumDecodable {
  static let defaultDecoderValue: MediaType = .other

  case audio = "AUDIO"
  case multipleChoices = "MULTIPLE_CHOICES"
  case other
}

또한 잘못된 열거값이 지정된 경우 값을 기본값으로 지정하는 대신 오류를 발생시키는 경우 쉽게 확장할 수 있습니다.이 변경 사항이 있는 요지는 https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128 에서 확인할 수 있습니다.
코드는 Swift 4.1/X 코드 9.3을 사용하여 컴파일 및 테스트되었습니다.

@proxpero 응답의 변형인 에스테르는 디코더를 다음과 같이 공식화하는 것입니다.

public init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    guard let key = values.allKeys.first else { throw err("No valid keys in: \(values)") }
    func dec<T: Decodable>() throws -> T { return try values.decode(T.self, forKey: key) }

    switch key {
    case .count: self = try .count(dec())
    case .title: self = try .title(dec())
    }
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case .count(let x): try container.encode(x, forKey: .count)
    case .title(let x): try container.encode(x, forKey: .title)
    }
}

이를 통해 컴파일러는 대소문자를 철저히 검증할 수 있으며, 인코딩된 값이 키의 예상 값과 일치하지 않는 경우에 대한 오류 메시지를 억제하지 않습니다.

실제로 위의 답변은 정말 훌륭하지만, 지속적으로 개발되는 클라이언트/서버 프로젝트에서 많은 사람들이 필요로 하는 것에 대한 세부 정보가 누락되어 있습니다.우리는 백엔드가 시간이 지남에 따라 지속적으로 진화하는 동안 앱을 개발합니다. 이는 일부 열거형 사례가 이러한 진화를 변화시킬 것임을 의미합니다.따라서 우리는 알려지지 않은 사례를 포함하는 열거형 디코딩 전략이 필요합니다.그렇지 않으면 배열이 포함된 개체를 디코딩할 수 없습니다.

제가 한 일은 아주 간단합니다.

enum Direction: String, Decodable {
    case north, south, east, west
}

struct DirectionList {
   let directions: [Direction]
}

extension DirectionList: Decodable {

    public init(from decoder: Decoder) throws {

        var container = try decoder.unkeyedContainer()

        var directions: [Direction] = []

        while !container.isAtEnd {

            // Here we just decode the string from the JSON which always works as long as the array element is a string
            let rawValue = try container.decode(String.self)

            guard let direction = Direction(rawValue: rawValue) else {
                // Unknown enum value found - ignore, print error to console or log error to analytics service so you'll always know that there are apps out which cannot decode enum cases!
                continue
            }
            // Add all known enum cases to the list of directions
            directions.append(direction)
        }
        self.directions = directions
    }
}

보너스 : 구현 숨기기 > 컬렉션으로 만들기

구현 세부 정보를 숨기는 것은 항상 좋은 생각입니다.이를 위해서는 코드가 조금만 더 필요합니다.요령은 순응하는 것입니다.DirectionsList로.Collection그리고 당신의 내부를 만듭니다.list배열 개인:

struct DirectionList {

    typealias ArrayType = [Direction]

    private let directions: ArrayType
}

extension DirectionList: Collection {

    typealias Index = ArrayType.Index
    typealias Element = ArrayType.Element

    // The upper and lower bounds of the collection, used in iterations
    var startIndex: Index { return directions.startIndex }
    var endIndex: Index { return directions.endIndex }

    // Required subscript, based on a dictionary index
    subscript(index: Index) -> Element {
        get { return directions[index] }
    }

    // Method that returns the next index when iterating
    func index(after i: Index) -> Index {
        return directions.index(after: i)
    }
}

맞춤형 컬렉션 준수에 대한 자세한 내용은 John Sundell의 블로그 게시물에서 확인할 수 있습니다. https://medium.com/ @johnsundell/creatoring-custom-collections-in-bba344e25d0b0

당신은 당신이 원하는 것을 할 수 있지만, 그것은 약간 관련이 있습니다 :(

import Foundation

enum PostType: Codable {
    case count(number: Int)
    case comment(text: String)

    init(from decoder: Decoder) throws {
        self = try PostTypeCodableForm(from: decoder).enumForm()
    }

    func encode(to encoder: Encoder) throws {
        try PostTypeCodableForm(self).encode(to: encoder)
    }
}

struct PostTypeCodableForm: Codable {
    // All fields must be optional!
    var countNumber: Int?
    var commentText: String?

    init(_ enumForm: PostType) {
        switch enumForm {
        case .count(let number):
            countNumber = number
        case .comment(let text):
            commentText = text
        }
    }

    func enumForm() throws -> PostType {
        if let number = countNumber {
            guard commentText == nil else {
                throw DecodeError.moreThanOneEnumCase
            }
            return .count(number: number)
        }
        if let text = commentText {
            guard countNumber == nil else {
                throw DecodeError.moreThanOneEnumCase
            }
            return .comment(text: text)
        }
        throw DecodeError.noRecognizedContent
    }

    enum DecodeError: Error {
        case noRecognizedContent
        case moreThanOneEnumCase
    }
}

let test = PostType.count(number: 3)
let data = try JSONEncoder().encode(test)
let string = String(data: data, encoding: .utf8)!
print(string) // {"countNumber":3}
let result = try JSONDecoder().decode(PostType.self, from: data)
print(result) // count(3)

특징들

  • 간편한 사용.디코딩 가능 인스턴스의 한 줄: line eglet enum: DecodableEnum<AnyEnum>
  • 표준 매핑 메커니즘으로 디코딩됩니다.JSONDecoder().decode(Model.self, from: data)
  • 알 수 없는 데이터를 수신하는 경우(예: 매핑)Decodable예기치 않은 데이터를 수신해도 개체에 장애가 발생하지 않습니다.
  • 손잡이/손잡이mapping또는decoding오류들

세부 사항

  • Xcode 12.0.1(12A7300)
  • 스위프트 5.3

해결책

import Foundation

enum DecodableEnum<Enum: RawRepresentable> where Enum.RawValue == String {
    case value(Enum)
    case error(DecodingError)

    var value: Enum? {
        switch self {
        case .value(let value): return value
        case .error: return nil
        }
    }

    var error: DecodingError? {
        switch self {
        case .value: return nil
        case .error(let error): return error
        }
    }

    enum DecodingError: Error {
        case notDefined(rawValue: String)
        case decoding(error: Error)
    }
}

extension DecodableEnum: Decodable {
    init(from decoder: Decoder) throws {
        do {
            let rawValue = try decoder.singleValueContainer().decode(String.self)
            guard let layout = Enum(rawValue: rawValue) else {
                self = .error(.notDefined(rawValue: rawValue))
                return
            }
            self = .value(layout)
        } catch let err {
            self = .error(.decoding(error: err))
        }
    }
}

사용샘플

enum SimpleEnum: String, Codable {
    case a, b, c, d
}

struct Model: Decodable {
    let num: Int
    let str: String
    let enum1: DecodableEnum<SimpleEnum>
    let enum2: DecodableEnum<SimpleEnum>
    let enum3: DecodableEnum<SimpleEnum>
    let enum4: DecodableEnum<SimpleEnum>?
}

let dictionary: [String : Any] = ["num": 1, "str": "blablabla", "enum1": "b", "enum2": "_", "enum3": 1]

let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.enum1.value)")
print("2. \(object.enum2.error)")
print("3. \(object.enum3.error)")
print("4. \(object.enum4)")

여기에 이미지 설명 입력

여기에는 여러 가지 좋은 접근법이 있지만, 하나 이상의 값을 가진 열거형을 논의하는 것을 본 적이 없습니다. 예를 들어 추론할 수는 있지만, 누군가 이에 대한 용도를 찾을 수도 있습니다.

import Foundation

enum Tup {
  case frist(String, next: Int)
  case second(Int, former: String)
  
  enum TupType: String, Codable {
    case first
    case second
  }
  enum CodingKeys: String, CodingKey {
    case type
    
    case first
    case firstNext
    
    case second
    case secondFormer
  }
  
}

extension Tup: Codable {
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    let type = try values.decode(TupType.self, forKey: .type)
    switch type {
    case .first:
      let str = try values.decode(String.self, forKey: .first)
      let next = try values.decode(Int.self, forKey: .firstNext)
      self = .frist(str, next: next)
    case .second:
      let int = try values.decode(Int.self, forKey: .second)
      let former = try values.decode(String.self, forKey: .secondFormer)
      self = .second(int, former: former)
    }
  }
  
  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    
    switch self {
    case .frist(let str, next: let next):
      try container.encode(TupType.first, forKey: .type)
      try container.encode(str, forKey: .first)
      try container.encode(next, forKey: .firstNext)
    case .second(let int, former: let former):
      try container.encode(TupType.second, forKey: .type)
      try container.encode(int, forKey: .second)
      try container.encode(former, forKey: .secondFormer)
    }
    
  }
}

let example1 = Tup.frist("123", next: 90)
do {
  let encoded = try JSONEncoder().encode(example1)
  print(encoded)
  
  let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
  print("decoded 1 = \(decoded)")
}
catch {
  print("errpr = \(error.localizedDescription)")
}


let example2 = Tup.second(10, former: "dantheman")

do {
  let encoded = try JSONEncoder().encode(example2)
  print(encoded)
  
  let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
  print("decoded 2 = \(decoded)")
}
catch {
  print("errpr = \(error.localizedDescription)")
}

다음은 Swift에서 열거형 디코딩 가능한 방법의 간단한 예입니다.

샘플 JSON:

[
    {
        "title": "1904",
        "artist": "The Tallest Man on Earth",
        "year": "2012",
        "type": "hindi"
    },
    {
        "title": "#40",
        "artist": "Dave Matthews",
        "year": "1999",
        "type": "english"
    },
    {
        "title": "40oz to Freedom",
        "artist": "Sublime",
        "year": "1996",
        "type": "english"
    },
    {
        "title": "#41",
        "artist": "Dave Matthews",
        "year": "1996",
        "type": "punjabi"
    }
]

모델 구조:

struct Song: Codable {
    
    public enum SongType: String, Codable {
        case hindi = "hindi"
        case english = "english"
        case punjabi = "punjabi"
        case tamil = "tamil"
        case none = "none"
    }
    
    let title: String
    let artist: String
    let year: String
    let type: SongType?
}

이제 JSON 파일을 구문 분석하고 데이터를 아래와 같은 노래 배열로 구문 분석할 수 있습니다.

func decodeJSON() {
    do {
        // creating path from main bundle and get data object from the path
        if let bundlePath = Bundle.main.path(forResource: "sample", ofType: "json"),
           let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
            
            // decoding an array of songs
            let songs = try JSONDecoder().decode([Song].self, from: jsonData)
            
            // printing the type of song
            songs.forEach { song in
                print("Song type: \(song.type?.rawValue ?? "")")
            }
        }
    } catch {
        print(error)
    }
}

문의 사항이 있을 경우 아래에 설명해 주십시오.

언급URL : https://stackoverflow.com/questions/44580719/how-do-i-make-an-enum-decodable-in-swift

반응형