고객 개체에 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
}
그런 다음이 유형은 Codable
및 Equatable
.
답변
아래와 같은 디코드 방법을 사용하여 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.0 이 Codable
프로토콜 지원합니다. 선언 만하면 metadata: JSON
모든 준비가 완료됩니다.
import SwiftyJSON
struct Customer {
let id: String
let email: String
let metadata: JSON
}
답변
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)!)