[json] Swift 4 디코딩 가능한 프로토콜에서 JSON 사전 유형으로 속성을 디코딩하는 방법

고객 개체에 JSON 사전을 포함 할 수 Customer있는 metadata속성을 포함하는 데이터 유형이 있다고 가정 해 보겠습니다.

struct Customer {
  let id: String
  let email: String
  let metadata: [String: Any]
}

{
  "object": "customer",
  "id": "4yq6txdpfadhbaqnwp3",
  "email": "john.doe@example.com",
  "metadata": {
    "link_id": "linked-id",
    "buy_count": 4
  }
}

metadata속성은 임의의 JSON 맵 오브젝트가 될 수 있습니다.

deserialized JSON에서 속성을 캐스팅하기 전에 NSJSONDeserialization새로운 Swift 4 Decodable프로토콜을 사용하면 여전히 그렇게 할 방법을 생각할 수 없습니다.

Decodable 프로토콜로 Swift 4에서 이것을 달성하는 방법을 아는 사람이 있습니까?



답변

내가 찾은 이 요점 에서 영감을 얻어 UnkeyedDecodingContainer및에 대한 확장을 작성했습니다 KeyedDecodingContainer. 여기 에서 내 요점에 대한 링크를 찾을 수 있습니다 . 이제이 코드를 사용 하여 익숙한 구문으로 Array<Any>또는 모든 것을 디코딩 할 수 있습니다 Dictionary<String, Any>.

let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)

또는

let array: [Any] = try container.decode([Any].self, forKey: key)

편집 : 사전 배열을 디코딩하는 한 가지주의 사항 [[String: Any]]이 있습니다. 필요한 구문은 다음과 같습니다. 강제 캐스팅 대신 오류를 던지고 싶을 것입니다.

let items: [[String: Any]] = try container.decode(Array<Any>.self, forKey: .items) as! [[String: Any]]

편집 2 : 단순히 전체 파일을 사전으로 변환하려는 경우 JSONDecoder 자체를 확장하여 사전을 직접 디코딩하는 방법을 찾지 못했기 때문에 JSONSerialization의 api를 사용하는 것이 좋습니다.

guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
  // appropriate error handling
  return
}

확장

// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a

struct JSONCodingKeys: CodingKey {
    var stringValue: String

    init?(stringValue: String) {
        self.stringValue = stringValue
    }

    var intValue: Int?

    init?(intValue: Int) {
        self.init(stringValue: "\(intValue)")
        self.intValue = intValue
    }
}


extension KeyedDecodingContainer {

    func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> {
        let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? {
        guard contains(key) else {
            return nil
        }
        guard try decodeNil(forKey: key) == false else {
            return nil
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> {
        var container = try self.nestedUnkeyedContainer(forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? {
        guard contains(key) else {
            return nil
        }
        guard try decodeNil(forKey: key) == false else {
            return nil
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
        var dictionary = Dictionary<String, Any>()

        for key in allKeys {
            if let boolValue = try? decode(Bool.self, forKey: key) {
                dictionary[key.stringValue] = boolValue
            } else if let stringValue = try? decode(String.self, forKey: key) {
                dictionary[key.stringValue] = stringValue
            } else if let intValue = try? decode(Int.self, forKey: key) {
                dictionary[key.stringValue] = intValue
            } else if let doubleValue = try? decode(Double.self, forKey: key) {
                dictionary[key.stringValue] = doubleValue
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedDictionary
            } else if let nestedArray = try? decode(Array<Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedArray
            }
        }
        return dictionary
    }
}

extension UnkeyedDecodingContainer {

    mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> {
        var array: [Any] = []
        while isAtEnd == false {
            // See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays.
            if try decodeNil() {
                continue
            } else if let value = try? decode(Bool.self) {
                array.append(value)
            } else if let value = try? decode(Double.self) {
                array.append(value)
            } else if let value = try? decode(String.self) {
                array.append(value)
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) {
                array.append(nestedDictionary)
            } else if let nestedArray = try? decode(Array<Any>.self) {
                array.append(nestedArray)
            }
        }
        return array
    }

    mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {

        let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self)
        return try nestedContainer.decode(type)
    }
}


답변

나도이 문제를 가지고 놀았고 마침내 “일반 JSON”유형으로 작업하기위한 간단한 라이브러리를 작성했습니다 . (여기서 “일반”은 “사전에 알려진 구조 없음”을 의미합니다.) 요점은 구체적인 유형으로 일반 JSON을 나타내는 것입니다.

public enum JSON {
    case string(String)
    case number(Float)
    case object([String:JSON])
    case array([JSON])
    case bool(Bool)
    case null
}

그런 다음이 유형은 CodableEquatable.


답변

아래와 같은 디코드 방법을 사용하여 Decodable프로토콜 을 확인하는 메타 데이터 구조체를 생성 하고 JSONDecoder클래스를 사용하여 데이터로부터 객체를 생성 할 수 있습니다.

let json: [String: Any] = [
    "object": "customer",
    "id": "4yq6txdpfadhbaqnwp3",
    "email": "john.doe@example.com",
    "metadata": [
        "link_id": "linked-id",
        "buy_count": 4
    ]
]

struct Customer: Decodable {
    let object: String
    let id: String
    let email: String
    let metadata: Metadata
}

struct Metadata: Decodable {
    let link_id: String
    let buy_count: Int
}

let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)

let decoder = JSONDecoder()
do {
    let customer = try decoder.decode(Customer.self, from: data)
    print(customer)
} catch {
    print(error.localizedDescription)
}


답변

나는 약간 다른 해결책을 가지고 왔습니다.

[String: Any]Any가 배열, 중첩 된 사전 또는 배열의 사전 일 수 있다는 단순한 구문 분석 이상의 무언가가 있다고 가정 해 보겠습니다 .

이 같은:

var json = """
{
  "id": 12345,
  "name": "Giuseppe",
  "last_name": "Lanza",
  "age": 31,
  "happy": true,
  "rate": 1.5,
  "classes": ["maths", "phisics"],
  "dogs": [
    {
      "name": "Gala",
      "age": 1
    }, {
      "name": "Aria",
      "age": 3
    }
  ]
}
"""

글쎄, 이것이 내 해결책입니다.

public struct AnyDecodable: Decodable {
  public var value: Any

  private struct CodingKeys: CodingKey {
    var stringValue: String
    var intValue: Int?
    init?(intValue: Int) {
      self.stringValue = "\(intValue)"
      self.intValue = intValue
    }
    init?(stringValue: String) { self.stringValue = stringValue }
  }

  public init(from decoder: Decoder) throws {
    if let container = try? decoder.container(keyedBy: CodingKeys.self) {
      var result = [String: Any]()
      try container.allKeys.forEach { (key) throws in
        result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value
      }
      value = result
    } else if var container = try? decoder.unkeyedContainer() {
      var result = [Any]()
      while !container.isAtEnd {
        result.append(try container.decode(AnyDecodable.self).value)
      }
      value = result
    } else if let container = try? decoder.singleValueContainer() {
      if let intVal = try? container.decode(Int.self) {
        value = intVal
      } else if let doubleVal = try? container.decode(Double.self) {
        value = doubleVal
      } else if let boolVal = try? container.decode(Bool.self) {
        value = boolVal
      } else if let stringVal = try? container.decode(String.self) {
        value = stringVal
      } else {
        throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
      }
    } else {
      throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
    }
  }
}

사용해보십시오

let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any]
print(stud)


답변

이전 답변을 찾았을 때 간단한 JSON 객체 케이스 만 테스트했지만 @slurmomatic 및 @zoul과 같은 런타임 예외가 발생하는 빈 케이스는 테스트하지 않았습니다. 이 문제로 죄송합니다.

그래서 간단한 JSONValue 프로토콜을 사용하여 다른 방법을 시도하고 AnyJSONValue유형 삭제 구조체를 구현하고 대신 해당 유형을 사용합니다 Any. 다음은 구현입니다.

public protocol JSONType: Decodable {
    var jsonValue: Any { get }
}

extension Int: JSONType {
    public var jsonValue: Any { return self }
}
extension String: JSONType {
    public var jsonValue: Any { return self }
}
extension Double: JSONType {
    public var jsonValue: Any { return self }
}
extension Bool: JSONType {
    public var jsonValue: Any { return self }
}

public struct AnyJSONType: JSONType {
    public let jsonValue: Any

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()

        if let intValue = try? container.decode(Int.self) {
            jsonValue = intValue
        } else if let stringValue = try? container.decode(String.self) {
            jsonValue = stringValue
        } else if let boolValue = try? container.decode(Bool.self) {
            jsonValue = boolValue
        } else if let doubleValue = try? container.decode(Double.self) {
            jsonValue = doubleValue
        } else if let doubleValue = try? container.decode(Array<AnyJSONType>.self) {
            jsonValue = doubleValue
        } else if let doubleValue = try? container.decode(Dictionary<String, AnyJSONType>.self) {
            jsonValue = doubleValue
        } else {
            throw DecodingError.typeMismatch(JSONType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON tyep"))
        }
    }
}

디코딩 할 때 사용하는 방법은 다음과 같습니다.

metadata = try container.decode ([String: AnyJSONValue].self, forKey: .metadata)

이 문제의 문제는 value.jsonValue as? Int. 우리 Conditional Conformance는 Swift에 착륙 할 때까지 기다려야 합니다. 그러면이 문제가 해결되거나 적어도 더 나아질 수 있습니다.


[이전 답변]

이 질문을 Apple Developer 포럼에 게시했는데 매우 쉽습니다.

내가 할 수있는

metadata = try container.decode ([String: Any].self, forKey: .metadata)

이니셜 라이저에서.

애초에 그것을 놓친 것은 내 잘못이었습니다.


답변

당신이 사용하는 경우 SwiftyJSON를 JSON을 구문 분석, 당신은 업데이트 할 수 있습니다 4.1.0Codable프로토콜 지원합니다. 선언 만하면 metadata: JSON모든 준비가 완료됩니다.

import SwiftyJSON

struct Customer {
  let id: String
  let email: String
  let metadata: JSON
}


답변

BeyovaJSON보셨을 것입니다.

import BeyovaJSON

struct Customer: Codable {
  let id: String
  let email: String
  let metadata: JToken
}

//create a customer instance

customer.metadata = ["link_id": "linked-id","buy_count": 4]

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
print(String(bytes: try! encoder.encode(customer), encoding: .utf8)!)