简体   繁体   English

如何在 Swift [45] 可解码协议中解码 JSON 字典类型的属性

[英]How to decode a property with type of JSON dictionary in Swift [45] decodable protocol

Let's say I have Customer data type which contains a metadata property that can contains any JSON dictionary in the customer object假设我有Customer数据类型,它包含一个metadata属性,该属性可以包含客户对象中的任何 JSON 字典

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
  }
}

The metadata property can be any arbitrary JSON map object. metadata属性可以是任意 JSON 地图对象。

Before I can cast the property from a deserialized JSON from NSJSONDeserialization but with the new Swift 4 Decodable protocol, I still can't think of a way to do that.在我可以从NSJSONDeserialization的反序列化 JSON 中转换属性但使用新的 Swift 4 Decodable协议之前,我仍然想不出办法来做到这一点。

Do anyone know how to achieve this in Swift 4 with Decodable protocol?有谁知道如何在 Swift 4 中使用 Decodable 协议来实现这一点?

With some inspiration from this gist I found, I wrote some extensions for UnkeyedDecodingContainer and KeyedDecodingContainer .我从这个要点中找到了一些灵​​感,我为UnkeyedDecodingContainerKeyedDecodingContainer编写了一些扩展。 You can find a link to my gist here .你可以在这里找到我的要点的链接。 By using this code you can now decode any Array<Any> or Dictionary<String, Any> with the familiar syntax:通过使用此代码,您现在可以使用熟悉的语法解码任何Array<Any>Dictionary<String, Any>

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

or或者

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

Edit: there is one caveat I have found which is decoding an array of dictionaries [[String: Any]] The required syntax is as follows.编辑:我发现有一个警告是解码一个字典数组[[String: Any]]所需的语法如下。 You'll likely want to throw an error instead of force casting:您可能希望抛出错误而不是强制转换:

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

EDIT 2: If you simply want to convert an entire file to a dictionary, you are better off sticking with api from JSONSerialization as I have not figured out a way to extend JSONDecoder itself to directly decode a dictionary.编辑 2:如果您只是想将整个文件转换为字典,最好坚持使用 JSONSerialization 中的 api,因为我还没有想出一种方法来扩展 JSONDecoder 本身以直接解码字典。

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

The extensions扩展名

// 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)
    }
}

I have played with this problem, too, and finally wrote a simple library for working with “generic JSON” types .我也玩过这个问题,最后写了一个简单的库来处理“通用 JSON”类型 (Where “generic” means “with no structure known in advance”.) Main point is representing the generic JSON with a concrete type: (其中“通用”的意思是“没有事先知道的结构”。)要点是用具体类型表示通用 JSON:

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

This type can then implement Codable and Equatable .这种类型然后可以实现CodableEquatable

You can create metadata struct which confirms to Decodable protocol and use JSONDecoder class to create object from data by using decode method like below您可以创建确认可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)
}

I came with a slightly different solution.我带来了一个稍微不同的解决方案。

Let's suppose we have something more than a simple [String: Any] to parse were Any might be an array or a nested dictionary or a dictionary of arrays.假设我们要解析的不仅仅是一个简单的[String: Any] ,Any 可能是一个数组、嵌套字典或数组字典。

Something like this:像这样的东西:

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
    }
  ]
}
"""

Well, this is my solution:嗯,这是我的解决方案:

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"))
    }
  }
}

Try it using尝试使用

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

When I found the old answer, I only tested a simple JSON object case but not an empty one which will cause a runtime exception like @slurmomatic and @zoul found.当我找到旧答案时,我只测试了一个简单的 JSON 对象案例,而不是一个空的案例,它会导致像 @slurmomatic 和 @zoul found 这样的运行时异常。 Sorry for this issue.抱歉这个问题。

So I try another way by having a simple JSONValue protocol, implement the AnyJSONValue type erasure struct and use that type instead of Any .所以我尝试了另一种方法,通过一个简单的 JSONValue 协议,实现AnyJSONValue类型擦除结构并使用该类型而不是Any Here's an implementation.这是一个实现。

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"))
        }
    }
}

And here is how to use it when decoding这是解码时如何使用它

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

The problem with this issue is that we must call value.jsonValue as? Int这个问题的问题是我们必须将value.jsonValue as? Int value.jsonValue as? Int . value.jsonValue as? Int We need to wait until Conditional Conformance land in Swift, that would solve this problem or at least help it to be better.我们需要等到 Swift 中的Conditional Conformance落地,这将解决这个问题,或者至少帮助它变得更好。


[Old Answer] [旧答案]

I post this question on the Apple Developer forum and it turns out it is very easy.我在 Apple Developer 论坛上发布了这个问题,结果证明这很容易。

I can do我可以

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

in the initializer.在初始化程序中。

It was my bad to miss that in the first place.一开始就错过它是我的坏事。

If you use SwiftyJSON to parse JSON, you can update to 4.1.0 which has Codable protocol support.如果您使用SwiftyJSON来解析 JSON,您可以更新到具有Codable协议支持的4.1.0 Just declare metadata: JSON and you're all set.只需声明metadata: JSON

import SwiftyJSON

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

You might have a look at BeyovaJSON你可能看看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)!)

I have made a pod to facilitate the way the decoding + encoding [String: Any] , [Any] .我制作了一个 pod 以方便解码 + 编码[String: Any] , [Any] And this provides encode or decode the optional properties, here https://github.com/levantAJ/AnyCodable这提供了编码或解码的可选属性,这里是https://github.com/levantAJ/AnyCodable

pod 'DynamicCodable', '1.0'

How to use it:如何使用它:

import DynamicCodable

struct YourObject: Codable {
    var dict: [String: Any]
    var array: [Any]
    var optionalDict: [String: Any]?
    var optionalArray: [Any]?

    enum CodingKeys: String, CodingKey {
        case dict
        case array
        case optionalDict
        case optionalArray
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        dict = try values.decode([String: Any].self, forKey: .dict)
        array = try values.decode([Any].self, forKey: .array)
        optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict)
        optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(dict, forKey: .dict)
        try container.encode(array, forKey: .array)
        try container.encodeIfPresent(optionalDict, forKey: .optionalDict)
        try container.encodeIfPresent(optionalArray, forKey: .optionalArray)
    }
}

Here is more generic (not only [String: Any] , but [Any] can decoded) and encapsulated approach (separate entity is used for that) inspired by @loudmouth answer.这是更通用的(不仅[String: Any] ,而且[Any]可以解码)和封装方法(使用单独的实体),灵感来自@loudmouth 答案。

Using it will look like:使用它看起来像:

extension Customer: Decodable {
  public init(from decoder: Decoder) throws {
    let selfContainer = try decoder.container(keyedBy: CodingKeys.self)
    id = try selfContainer.decode(.id)
    email = try selfContainer.decode(.email)
    let metadataContainer: JsonContainer = try selfContainer.decode(.metadata)
    guard let metadata = metadataContainer.value as? [String: Any] else {
      let context = DecodingError.Context(codingPath: [CodingKeys.metadata], debugDescription: "Expected '[String: Any]' for 'metadata' key")
      throw DecodingError.typeMismatch([String: Any].self, context)
    }
    self.metadata = metadata
  }

  private enum CodingKeys: String, CodingKey {
    case id, email, metadata
  }
}

JsonContainer is a helper entity we use to wrap decoding JSON data to JSON object (either array or dictionary) without extending *DecodingContainer (so it won't interfere with rare cases when a JSON object is not meant by [String: Any] ). JsonContainer是一个辅助实体,我们用来将解码 JSON 数据包装到 JSON 对象(数组或字典)而不扩展*DecodingContainer (因此它不会干扰[String: Any]不表示 JSON 对象的罕见情况)。

struct JsonContainer {

  let value: Any
}

extension JsonContainer: Decodable {

  public init(from decoder: Decoder) throws {
    if let keyedContainer = try? decoder.container(keyedBy: Key.self) {
      var dictionary = [String: Any]()
      for key in keyedContainer.allKeys {
        if let value = try? keyedContainer.decode(Bool.self, forKey: key) {
          // Wrapping numeric and boolean types in `NSNumber` is important, so `as? Int64` or `as? Float` casts will work
          dictionary[key.stringValue] = NSNumber(value: value)
        } else if let value = try? keyedContainer.decode(Int64.self, forKey: key) {
          dictionary[key.stringValue] = NSNumber(value: value)
        } else if let value = try? keyedContainer.decode(Double.self, forKey: key) {
          dictionary[key.stringValue] = NSNumber(value: value)
        } else if let value = try? keyedContainer.decode(String.self, forKey: key) {
          dictionary[key.stringValue] = value
        } else if (try? keyedContainer.decodeNil(forKey: key)) ?? false {
          // NOP
        } else if let value = try? keyedContainer.decode(JsonContainer.self, forKey: key) {
          dictionary[key.stringValue] = value.value
        } else {
          throw DecodingError.dataCorruptedError(forKey: key, in: keyedContainer, debugDescription: "Unexpected value for \(key.stringValue) key")
        }
      }
      value = dictionary
    } else if var unkeyedContainer = try? decoder.unkeyedContainer() {
      var array = [Any]()
      while !unkeyedContainer.isAtEnd {
        let container = try unkeyedContainer.decode(JsonContainer.self)
        array.append(container.value)
      }
      value = array
    } else if let singleValueContainer = try? decoder.singleValueContainer() {
      if let value = try? singleValueContainer.decode(Bool.self) {
        self.value = NSNumber(value: value)
      } else if let value = try? singleValueContainer.decode(Int64.self) {
        self.value = NSNumber(value: value)
      } else if let value = try? singleValueContainer.decode(Double.self) {
        self.value = NSNumber(value: value)
      } else if let value = try? singleValueContainer.decode(String.self) {
        self.value = value
      } else if singleValueContainer.decodeNil() {
        value = NSNull()
      } else {
        throw DecodingError.dataCorruptedError(in: singleValueContainer, debugDescription: "Unexpected value")
      }
    } else {
      let context = DecodingError.Context(codingPath: [], debugDescription: "Invalid data format for JSON")
      throw DecodingError.dataCorrupted(context)
    }
  }

  private struct Key: CodingKey {
    var stringValue: String

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

    var intValue: Int?

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

Note that numberic and boolean types are backed by NSNumber , else something like this won't work:请注意,数字和布尔类型由NSNumber支持,否则这样的事情将不起作用:

if customer.metadata["keyForInt"] as? Int64 { // as it always will be nil

decode using decoder and coding keys使用解码器和编码密钥解码

public let dataToDecode: [String: AnyDecodable]

enum CodingKeys: CodingKey {
    case dataToDecode
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.dataToDecode = try container.decode(Dictionary<String, AnyDecodable>.self, forKey: .dataToDecode) 
}    

Details细节

  • Xcode 12.0.1 (12A7300) Xcode 12.0.1 (12A7300)
  • Swift 5.3斯威夫特 5.3

Based on Tai Le library基于太乐

// code from: https://github.com/levantAJ/AnyCodable/blob/master/AnyCodable/DecodingContainer%2BAnyCollection.swift

private
struct AnyCodingKey: CodingKey {
    let stringValue: String
    private (set) var intValue: Int?
    init?(stringValue: String) { self.stringValue = stringValue }
    init?(intValue: Int) {
        self.intValue = intValue
        stringValue = String(intValue)
    }
}

extension KeyedDecodingContainer {

    private
    func decode(_ type: [Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [Any] {
        var values = try nestedUnkeyedContainer(forKey: key)
        return try values.decode(type)
    }

    private
    func decode(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [String: Any] {
        try nestedContainer(keyedBy: AnyCodingKey.self, forKey: key).decode(type)
    }

    func decode(_ type: [String: Any].Type) throws -> [String: Any] {
        var dictionary: [String: Any] = [:]
        for key in allKeys {
            if try decodeNil(forKey: key) {
                dictionary[key.stringValue] = NSNull()
            } else if let bool = try? decode(Bool.self, forKey: key) {
                dictionary[key.stringValue] = bool
            } else if let string = try? decode(String.self, forKey: key) {
                dictionary[key.stringValue] = string
            } else if let int = try? decode(Int.self, forKey: key) {
                dictionary[key.stringValue] = int
            } else if let double = try? decode(Double.self, forKey: key) {
                dictionary[key.stringValue] = double
            } else if let dict = try? decode([String: Any].self, forKey: key) {
                dictionary[key.stringValue] = dict
            } else if let array = try? decode([Any].self, forKey: key) {
                dictionary[key.stringValue] = array
            }
        }
        return dictionary
    }
}

extension UnkeyedDecodingContainer {
    mutating func decode(_ type: [Any].Type) throws -> [Any] {
        var elements: [Any] = []
        while !isAtEnd {
            if try decodeNil() {
                elements.append(NSNull())
            } else if let int = try? decode(Int.self) {
                elements.append(int)
            } else if let bool = try? decode(Bool.self) {
                elements.append(bool)
            } else if let double = try? decode(Double.self) {
                elements.append(double)
            } else if let string = try? decode(String.self) {
                elements.append(string)
            } else if let values = try? nestedContainer(keyedBy: AnyCodingKey.self),
                let element = try? values.decode([String: Any].self) {
                elements.append(element)
            } else if var values = try? nestedUnkeyedContainer(),
                let element = try? values.decode([Any].self) {
                elements.append(element)
            }
        }
        return elements
    }
}

Solution解决方案

struct DecodableDictionary: Decodable {
    typealias Value = [String: Any]
    let dictionary: Value?
    init(from decoder: Decoder) throws {
        dictionary = try? decoder.container(keyedBy: AnyCodingKey.self).decode(Value.self)
    }
}

Usage用法

struct Model: Decodable {
    let num: Double?
    let flag: Bool?
    let dict: DecodableDictionary?
    let dict2: DecodableDictionary?
    let dict3: DecodableDictionary?
}

let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print(object.dict?.dictionary)      // prints [String: Any]
print(object.dict2?.dictionary)     // prints nil
print(object.dict3?.dictionary)     // prints nil

I have written an article and repo that helps in adding [String: Any] support for Codable for decoding as well as encoding.我写了一篇文章和repo ,有助于添加 [String: Any] 对 Codable 的解码和编码支持。

https://medium.com/nerd-for-tech/string-any-support-for-codable-4ba062ce62f2 https://medium.com/nerd-for-tech/string-any-support-for-codable-4ba062ce62f2

This improves on decodable aspect and also add encodable support as solution given by in https://stackoverflow.com/a/46049763/9160905这改进了可解码方面,并添加了可编码支持作为https://stackoverflow.com/a/46049763/9160905 中给出的解决方案

what you will be able to achieve:您将能够实现的目标:

json json

sample code示例代码

This will work这将起作用

public struct AnyDecodable: Decodable {
    public let value: Any

    public init<T>(_ value: T?) {
        self.value = value ?? ()
    }
}

let contentDecodable = try values.decodeIfPresent(AnyDecodable.self, forKey: .content)

I used some of the answers on this topic to get the simplest solution possible for me.我使用了有关该主题的一些答案来为我获得最简单的解决方案。 My problem is that I was receiving a [String: Any] type dictionary, but I could very well work with a [String: String] transforming every other Any value in String.我的问题是我收到了一个[String: Any]类型的字典,但我可以很好地使用一个[String: String]来转换 String 中的所有其他Any值。 So this is my solution:所以这是我的解决方案:

struct MetadataType: Codable {
    let value: String?

    private init(_ value: String?) {
        self.value = value
    }

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

        if let decodedValue = try? container.decode(Int.self) {
            self.init(String(decodedValue))
        } else if let decodedValue = try? container.decode(Double.self) {
            self.init(String(decodedValue))
        } else if let decodedValue = try? container.decode(Bool.self) {
            self.init(String(decodedValue))
        } else if let decodedValue = try? container.decode(String.self) {
            self.init(decodedValue)
        } else {
            self.init(nil)
        }
    }
}

And when declaring my dictionary, I use在声明我的字典时,我使用

let userInfo: [String: MetadataType]

What you want go against the design of Codable . 您想要的东西与Codable的设计Codable The idea behind Codable is to provide a mechanism to archive and unarchive data in a type-safe manner. Codable背后的想法是提供一种以类型安全的方式存档和取消存档数据的机制。 This means you must define the properties and their data types before hand. 这意味着您必须先定义属性及其数据类型。 I can think of 2 solutions to your problem: 我可以想到两种解决您的问题的方法:

1. List all potential metadata keys 1.列出所有潜在的元数据键

Oftentimes, if you reach deep enough into the API's documentation, you will find the full list of all potential metadata keys. 通常,如果您深入了解API文档,则会找到所有潜在元数据键的完整列表。 Define a Metadata struct, with these keys as optional properties: 定义Metadata结构,并将这些键作为可选属性:

struct Customer: Decodable {
    struct Metadata: Decodable {
        var linkId: String?
        var buyCount: Int?
        var somethingElse: Int?

        private enum CodingKeys: String, CodingKey {
            case linkId = "link_id"
            case buyCount = "buy_count"
            case somethingElse = "something_else"
        }
    }

    var object: String
    var id: String
    var email: String
    var metadata: Metadata
}

let customer = try! JSONDecoder().decode(Customer.self, from: jsonData)
print(customer.metadata)

I can see that the Swift designers would have preferred this approach. 我可以看到Swift设计师会更喜欢这种方法。


2. Combine Decodable and JSONSerialization 2.结合Decodable和JSONSerialization

JSONSerialization offers great dynamism in the trade off for type safety. JSONSerialization在权衡类型安全性JSONSerialization提供了极大的动力。 You can definitely mix it up with Decodable , whose design philosophy is just the opposite: 您绝对可以将它与Decodable混合使用,后者的设计理念恰恰相反:

struct Customer {
    private struct RawCustomer: Decodable {
        var object: String
        var id: String
        var email: String
    }

    var object: String
    var id: String
    var email: String
    var metadata: [String: AnyObject]

    init(jsonData: Data) throws {
        let rawCustomer = try JSONDecoder().decode(RawCustomer.self, from: jsonData)
        object = rawCustomer.object
        id     = rawCustomer.id
        email  = rawCustomer.email

        let jsonObject = try JSONSerialization.jsonObject(with: jsonData)
        if let dict = jsonObject as? [String: AnyObject],
            let metadata = dict["metadata"] as? [String: AnyObject]
        {
            self.metadata = metadata
        } else {
            self.metadata = [String: AnyObject]()
        }
    }
}

let customer = try! Customer(jsonData: jsonData)
print(customer.metadata)

The easiest and suggested way is to create separate model for each dictionary or model that is in JSON .最简单和建议的方法是为JSON 中的每个字典或模型创建单独的模型

Here is what I do这是我所做的

//Model for dictionary **Metadata**

struct Metadata: Codable {
    var link_id: String?
    var buy_count: Int?
}  

//Model for dictionary **Customer**

struct Customer: Codable {
   var object: String?
   var id: String?
   var email: String?
   var metadata: Metadata?
}

//Here is our decodable parser that decodes JSON into expected model

struct CustomerParser {
    var customer: Customer?
}

extension CustomerParser: Decodable {

//keys that matches exactly with JSON
enum CustomerKeys: String, CodingKey {
    case object = "object"
    case id = "id"
    case email = "email"
    case metadata = "metadata"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CustomerKeys.self) // defining our (keyed) container

    let object: String = try container.decode(String.self, forKey: .object) // extracting the data
    let id: String = try container.decode(String.self, forKey: .id) // extracting the data
    let email: String = try container.decode(String.self, forKey: .email) // extracting the data

   //Here I have used metadata model instead of dictionary [String: Any]
    let metadata: Metadata = try container.decode(Metadata.self, forKey: .metadata) // extracting the data

    self.init(customer: Customer(object: object, id: id, email: email, metadata: metadata))

    }
}

Usage:用法:

  if let url = Bundle.main.url(forResource: "customer-json-file", withExtension: "json") {
        do {
            let jsonData: Data =  try Data(contentsOf: url)
            let parser: CustomerParser = try JSONDecoder().decode(CustomerParser.self, from: jsonData)
            print(parser.customer ?? "null")

        } catch {

        }
    }

**I have used optional to be in safe side while parsing, can be changed as needed. **我在解析时使用了 optional 来保证安全,可以根据需要进行更改。

Read more on this topic 阅读有关此主题的更多信息

extension ViewController {

    func swiftyJson(){
        let url = URL(string: "https://itunes.apple.com/search?term=jack+johnson")
        //let url = URL(string: "http://makani.bitstaging.in/api/business/businesses_list")

        Alamofire.request(url!, method: .get, parameters: nil).responseJSON { response in
            var arrayIndexes = [IndexPath]()
            switch(response.result) {
            case .success(_):

                let data = response.result.value as! [String : Any]

                if let responseData =  Mapper<DataModel>().map(JSON: data) {
                    if responseData.results!.count > 0{
                        self.arrayExploreStylistList = []
                    }
                    for i in 0..<responseData.results!.count{
                        arrayIndexes.append(IndexPath(row: self.arrayExploreStylistList.count + i, section: 0))
                    }
                    self.arrayExploreStylistList.append(contentsOf: responseData.results!)

                    print(arrayIndexes.count)

                }

                //                    if let arrNew = data["results"] as? [[String : Any]]{
                //                        let jobData = Mapper<DataModel>().mapArray(JSONArray: arrNew)
                //                        print(jobData)
                //                        self.datamodel = jobData
                //                    }
                self.tblView.reloadData()
                break

            case .failure(_):
                print(response.result.error as Any)
                break

            }
        }

    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM