Dekodieren einer Eigenschaft mit dem Typ eines JSON-Wörterbuchs im decodierbaren Swift 4-Protokoll

103

Angenommen, ich habe einen CustomerDatentyp, der eine metadataEigenschaft enthält, die ein beliebiges JSON-Wörterbuch im Kundenobjekt enthalten kann

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

{  
  "object": "customer",
  "id": "4yq6txdpfadhbaqnwp3",
  "email": "[email protected]",
  "metadata": {
    "link_id": "linked-id",
    "buy_count": 4
  }
}

Die metadataEigenschaft kann ein beliebiges JSON-Kartenobjekt sein.

Bevor ich die Eigenschaft von einem deserialisierten JSON aus übertragen kann, NSJSONDeserializationaber mit dem neuen Swift 4- DecodableProtokoll, kann ich mir immer noch keine Möglichkeit vorstellen, dies zu tun.

Weiß jemand, wie dies in Swift 4 mit dem decodierbaren Protokoll erreicht werden kann?

Pitiphong Phongpattranont
quelle

Antworten:

88

Mit etwas Inspiration von diesem Kern, den ich gefunden habe, habe ich einige Erweiterungen für UnkeyedDecodingContainerund geschrieben KeyedDecodingContainer. Einen Link zu meinem Kern finden Sie hier . Mit diesem Code können Sie jetzt eine beliebige Array<Any>oder Dictionary<String, Any>mit der bekannten Syntax dekodieren :

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

oder

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

Bearbeiten: Es gibt eine Einschränkung, die ich gefunden habe, nämlich das Dekodieren eines Arrays von Wörterbüchern. [[String: Any]]Die erforderliche Syntax lautet wie folgt. Sie möchten wahrscheinlich einen Fehler auslösen, anstatt das Casting zu erzwingen:

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

EDIT 2: Wenn Sie einfach eine ganze Datei in ein Wörterbuch konvertieren möchten, sollten Sie sich besser an die API von JSONSerialization halten, da ich keine Möglichkeit gefunden habe, JSONDecoder selbst zu erweitern, um ein Wörterbuch direkt zu dekodieren.

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

Die Erweiterungen

// 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)
    }
}
Großmaul
quelle
Interessant, ich werde dieses Wesentliche versuchen und das Ergebnis für Sie aktualisieren @loudmouth
Pitiphong Phongpattranont
@PitiphongPhongpattranont Hat dieser Code für Sie funktioniert?
Großmaul
1
@JonBrooks die letzte Bedingung in die in UnkeyedDecodingContainer‚s decode(_ type: Array<Any>.Type) throws -> Array<Any>für eine prüfen verschachtelte Array. Wenn Sie also eine Datenstruktur haben, die wie folgt aussieht: [true, 452.0, ["a", "b", "c"] ] Sie würde das verschachtelte ["a", "b", "c"]Array ziehen. Die decodeMethode eines UnkeyedDecodingContainer"Pops" entfernt das Element aus dem Container. Es sollte keine unendliche Rekursion verursachen.
Großmaul
1
@loudmouth es ist möglich, keine Werte für Schlüssel in json zu haben : {"array": null}. Sie guard contains(key)werden also bestehen, aber einige Zeilen später abstürzen, wenn Sie versuchen, den Nullwert für den Schlüssel "array" zu dekodieren. Es ist daher besser, vor dem Aufruf eine weitere Bedingung hinzuzufügen, um zu überprüfen, ob der Wert tatsächlich nicht null ist decode.
Chebur
2
Ich fand eine Lösung: Anstatt zu } else if let nestedArray = try? decode(Array<Any>.self, forKey: key)versuchen:} else if var nestedContainer = try? nestedUnkeyedContainer(), let nestedArray = try? nestedContainer.decode(Array<Any>.self) {
Jon Brooks
23

Ich habe auch mit diesem Problem gespielt und schließlich eine einfache Bibliothek für die Arbeit mit „generischen JSON“ -Typen geschrieben . (Wobei "generisch" "ohne vorher bekannte Struktur" bedeutet.) Hauptpunkt ist die Darstellung des generischen JSON mit einem konkreten Typ:

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

Dieser Typ kann dann Codableund implementieren Equatable.

Zoul
quelle
13

Sie können eine Metadatenstruktur erstellen, die das DecodableProtokoll bestätigt, und mithilfe der JSONDecoderKlasse Objekte aus Daten erstellen, indem Sie die unten beschriebene Dekodierungsmethode verwenden

let json: [String: Any] = [
    "object": "customer",
    "id": "4yq6txdpfadhbaqnwp3",
    "email": "[email protected]",
    "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)
}
Suhit Patil
quelle
10
Nein, das kann ich nicht, da ich die Struktur des metadataWertes nicht kenne . Es kann ein beliebiges Objekt sein.
Pitiphong Phongpattranont
Meinst du, es kann entweder ein Array- oder ein Dictionary-Typ sein?
Suhit Patil
Können
2
Der Wert von metadatakann ein beliebiges JSON-Objekt sein. Es kann also ein leeres Wörterbuch oder ein beliebiges Wörterbuch sein. "Metadaten": {} "Metadaten": {Benutzer_ID: "ID"} "Metadaten": {Präferenz: {Shows_Wert: Wahr, Sprache: "En"}} usw.
Pitiphong Phongpattranont
Eine mögliche Option wäre, alle Parameter in der Metadatenstruktur als Optionen zu verwenden und alle möglichen Werte in der Metadatenstruktur wie Strukturmetadaten aufzulisten {var user_id: String? var Präferenz: String? }
Suhit Patil
8

Ich kam mit einer etwas anderen Lösung.

Nehmen wir an, wir haben mehr als nur eine einfache [String: Any]Analyse, bei der Any ein Array oder ein verschachteltes Wörterbuch oder ein Wörterbuch von Arrays sein könnte.

Etwas wie das:

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

Nun, das ist meine Lösung:

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

Probieren Sie es mit

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

Als ich die alte Antwort gefunden habe, habe ich nur einen einfachen JSON-Objektfall getestet, aber keinen leeren, der eine Laufzeitausnahme wie @slurmomatic und @zoul gefunden hat. Entschuldigung für dieses Problem.

Also versuche ich es auf andere Weise, indem ich ein einfaches JSONValue-Protokoll habe, die AnyJSONValueStruktur zum Löschen des Typs implementiere und stattdessen diesen Typ verwende Any. Hier ist eine Implementierung.

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

Und hier erfahren Sie, wie Sie es beim Dekodieren verwenden

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

Das Problem mit diesem Problem ist, dass wir anrufen müssen value.jsonValue as? Int. Wir müssen warten, bis wir Conditional Conformancein Swift landen, um dieses Problem zu lösen oder zumindest zu helfen, besser zu werden.


[Alte Antwort]

Ich poste diese Frage im Apple Developer Forum und es stellt sich heraus, dass es sehr einfach ist.

ich kann

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

im Initialisierer.

Es war mein schlechtes, das überhaupt zu verpassen.

Pitiphong Phongpattranont
quelle
4
Könnte den Link zur Frage auf Apple Developer posten. Anyentspricht nicht, Decodabledaher bin ich mir nicht sicher, wie dies die richtige Antwort ist.
Reza Shirazian
@RezaShirazian Das habe ich mir gedacht. Es stellt sich jedoch heraus, dass Dictionary Encodable entspricht, wenn seine Schlüssel Hashable entsprechen und nicht von seinen Werten abhängen. Sie können den Dictionary-Header öffnen und das selbst sehen. Erweiterungswörterbuch: Codierbar wo Schlüssel: Hashable Erweiterungswörterbuch: Decodierbar wo Schlüssel: Hashable forums.developer.apple.com/thread/80288#237680
Pitiphong Phongpattranont
6
Derzeit funktioniert dies nicht. "Dictionary <String, Any> entspricht nicht Decodable, weil Any nicht Decodable entspricht"
mbuchetics
Es stellt sich heraus, dass es funktioniert. Ich benutze es in meinem Code. Sie müssen verstehen, dass es keine Möglichkeit gibt, die Anforderung auszudrücken, dass "Der Wert des Wörterbuchs muss dem decodierbaren Protokoll entsprechen, damit das Wörterbuch dem decodierbaren Protokoll entspricht". Das ist die "bedingte Konformität", die in Swift 4 noch nicht implementiert ist. Ich denke, sie ist vorerst in Ordnung, da das Swift Type System (und die Generika) viele Einschränkungen aufweisen. Dies funktioniert vorerst, aber wenn sich das Swift Type System in Zukunft verbessert (insbesondere wenn die bedingte Konformität implementiert ist), sollte dies nicht funktionieren.
Pitiphong Phongpattranont
3
Funktioniert bei mir ab Xcode 9 Beta 5 nicht. Kompiliert, wird jedoch zur Laufzeit in die Luft gesprengt
Zoul
6

Wenn Sie SwiftyJSON zum Parsen von JSON verwenden, können Sie auf 4.1.0 aktualisieren, das CodableProtokollunterstützung bietet . Einfach deklarieren metadata: JSONund fertig.

import SwiftyJSON

struct Customer {
  let id: String
  let email: String
  let metadata: JSON
}
Allen Huang
quelle
Ich weiß nicht, warum diese Antwort abgelehnt wurde. Es ist absolut gültig und löst das Problem.
Leonid Usov
Es scheint gut für die Migration von SwiftyJSON nach Decodable zu sein
Michał Ziobro
Dies löst nicht, wie die Metadaten json analysiert werden sollen, die das ursprüngliche Problem waren.
Lamacorn
1

Vielleicht sehen Sie sich BeyovaJSON an

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)!)
canius
quelle
Oh, wirklich nett. Verwenden Sie diese Option, um einen generischen JSON als JToken zu empfangen, einige Werte anzuhängen und zum Server zurückzukehren. Wirklich sehr gut. Das ist eine großartige Arbeit, die du geleistet hast :)
Vitor Hugo Schwaab
1

Der einfachste und empfohlene Weg besteht darin , für jedes Wörterbuch oder Modell in JSON ein separates Modell zu erstellen .

Hier ist was ich tue

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

    }
}

Verwendung:

  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 {

        }
    }

** Ich habe optional verwendet, um beim Parsen auf der sicheren Seite zu sein. Kann bei Bedarf geändert werden.

Lesen Sie mehr zu diesem Thema

Minhazur
quelle
1
Ihre Antwort ist mit Sicherheit die richtige für Swift 4.1 und die erste Zeile Ihres Beitrags ist tot! Angenommen, die Daten stammen von einem Webdienst. Sie können einfache verschachtelte Objekte modellieren und dann mithilfe der Punktsyntax die einzelnen Objekte erfassen. Siehe suhits Antwort unten.
David H
1

Ich habe eine pod gemacht, wie die Decodierung + codiert , zu erleichtern [String: Any], [Any]. Und dies ermöglicht das Codieren oder Decodieren der optionalen Eigenschaften, hier https://github.com/levantAJ/AnyCodable

pod 'DynamicCodable', '1.0'

Wie man es benutzt:

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)
    }
}
Tai Le
quelle
0

Hier ist ein allgemeinerer (nicht nur [String: Any], aber [Any]auch entschlüsselbarer) und gekapselter Ansatz (dafür wird eine separate Entität verwendet), der von der Antwort von @loudmouth inspiriert ist.

Die Verwendung sieht folgendermaßen aus:

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

JsonContainerist eine Hilfsentität, die wir verwenden, um die Dekodierung von JSON-Daten in ein JSON-Objekt (entweder Array oder Wörterbuch) zu verpacken, ohne sie zu erweitern *DecodingContainer(damit seltene Fälle, in denen ein JSON-Objekt nicht gemeint ist, nicht beeinträchtigt werden [String: Any]).

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

Beachten Sie, dass numerische und boolesche Typen von unterstützt werden NSNumber, sonst funktioniert so etwas nicht:

if customer.metadata["keyForInt"] as? Int64 { // as it always will be nil
Alexey Kozhevnikov
quelle
Kann ich nur ausgewählte Eigenschaften dekodieren und andere automatisch dekodieren lassen, da ich 15 Eigenschaften habe, die für die automatische Dekodierung ausreichen, und möglicherweise 3, die eine benutzerdefinierte Dekodierungsbehandlung erfordern?
Michał Ziobro
@ MichałZiobro Möchten Sie, dass ein Teil der Daten in ein JSON-Objekt und ein Teil in separate Instanzvariablen dekodiert wird? Oder möchten Sie nur für einen Teil des Objekts einen Teildecodierungsinitialisierer schreiben (und er hat nichts mit einer JSON-ähnlichen Struktur gemeinsam)? Meines Wissens lautet die Antwort auf die erste Frage ja, auf die zweite nein.
Alexey Kozhevnikov
Ich möchte nur einige Eigenschaften mit angepasster Dekodierung und den Rest mit Standard-Standarddekodierung haben
Michał Ziobro
@ MichałZiobro Wenn ich dich richtig verstehe, ist es nicht möglich. Wie auch immer, Ihre Frage ist für die aktuelle SO-Frage nicht relevant und eine separate wert.
Alexey Kozhevnikov
0

Decodieren mit Decoder und Codierschlüsseln

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) 
}    
Ashim Dahal
quelle
-1
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

            }
        }

    }
}
Hiren
quelle