Как декодировать свойство с типом словаря JSON в декодируемом протоколе Swift [45]

Допустим, у меня есть тип данных Customer, который содержит свойство metadata, которое может содержать любой словарь JSON в объекте клиента.

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

Свойством metadata может быть любой произвольный объект карты JSON.

Прежде чем я смогу преобразовать свойство из десериализованного JSON из NSJSONDeserialization, но с новым протоколом Swift 4 Decodable, я все еще не могу придумать, как это сделать.

Кто-нибудь знает, как этого добиться в Swift 4 с протоколом Decodable?


person Pitiphong Phongpattranont    schedule 17.06.2017    source источник


Ответы (13)


Вдохновленный этой сутью, я написал несколько расширений для UnkeyedDecodingContainer и KeyedDecodingContainer. Ссылку на мою суть можно найти здесь. Используя этот код, вы теперь можете декодировать любые 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)

Изменить: я обнаружил одно предостережение, которое заключается в декодировании массива словарей [[String: Any]]. Требуемый синтаксис следующий. Скорее всего, вы захотите выдать ошибку вместо принудительного приведения:

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

РЕДАКТИРОВАТЬ 2: если вы просто хотите преобразовать весь файл в словарь, вам лучше придерживаться api из JSONSerialization, поскольку я не нашел способа расширить сам JSONDecoder для прямого декодирования словаря.

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)
    }
}
person loudmouth    schedule 05.09.2017
comment
Интересно, я попробую эту суть и сообщу вам результат @loudmouth - person Pitiphong Phongpattranont; 05.09.2017
comment
@PitiphongPhongpattranont, этот код сработал для вас? - person loudmouth; 21.09.2017
comment
Я бы сказал да. Я немного реорганизовал этот фрагмент, но ваша основная идея отлично работает. Спасибо - person Pitiphong Phongpattranont; 21.09.2017
comment
Зачем устанавливать значение true при декодировании нулевого значения ключа? - person dan; 21.12.2017
comment
@dan Я думаю, что вы правы, задав этот вопрос: я думаю, что условие для декодирования nil следует просто пропустить, так как сохранение nil в словаре, конечно, является запретным, и попытка извлечь несуществующее значение для ключа будет все равно вернуть nil. Отредактирую сейчас свой ответ ;-) - person loudmouth; 26.12.2017
comment
Я не вижу, как работает это решение: я получаю бесконечную рекурсию в UnkeyedDecodingContainer decode(_ type: Array<Any>.Type) throws -> Array<Any>, которая вызывает себя в последнем if выражении. - person Jon Brooks; 12.01.2018
comment
Я удалил свои предыдущие комментарии, потому что мой код не работал по моей вине. Теперь работает нормально. Спасибо большое. - person WedgeSparda; 18.01.2018
comment
@JonBrooks последнее условие в UnkeyedDecodingContainer decode(_ type: Array<Any>.Type) throws -> Array<Any> проверяет наличие вложенного массива. Итак, если у вас есть структура данных, которая выглядит следующим образом: [true, 452.0, ["a", "b", "c"] ] Будет извлечен вложенный массив ["a", "b", "c"]. Метод decode элемента UnkeyedDecodingContainer извлекает элемент из контейнера. Это не должно вызывать бесконечную рекурсию. - person loudmouth; 24.01.2018
comment
@loudmouth, не могли бы вы исправить все decodeIfPresent методы, добавив проверку, действительно ли ключ существует, но все еще имеет нулевое значение. Например: guard let isNil = try? decodeNil(forKey: key), !isNil else { return nil } - person chebur; 09.03.2018
comment
Эй, @chebur, ты просто говоришь, что decodeNil нужно добавить в конце в качестве проверки? Я не уверен, какова здесь цель, поскольку сохранение nil в словаре невозможно. Вы получите сбой с ошибкой типа error: nil is not compatible with expected dictionary value type 'Any'. Если значение nil в вашем JSON, вам лучше игнорировать его, поскольку попытка доступа к значению для ключа словаря в любом случае вернет nil. - person loudmouth; 14.03.2018
comment
@loudmouth в json может быть нулевое значение для ключей: {"array": null}. Таким образом, ваш guard contains(key) пройдет, но через несколько строк при попытке декодирования нулевого значения для массива ключей произойдет сбой. Поэтому перед вызовом decode лучше добавить еще одно условие, чтобы проверить, действительно ли значение не равно нулю. - person chebur; 15.03.2018
comment
Понятно @chebur! Скоро исправлю! - person loudmouth; 05.04.2018
comment
Приносим извинения за задержку с ответом. Я все еще получаю бесконечную рекурсию с приведенным выше кодом и легко вижу проблему (как я описал выше). Мне любопытно, все ли голоса протестировали этот случай ... Попробуйте этот тестовый пример: gist .github.com / jonbrooks / a2f0f19d8bcb00b51cf1b0567d06c720. - person Jon Brooks; 18.09.2018
comment
Нашел исправление: вместо } else if let nestedArray = try? decode(Array<Any>.self, forKey: key) попробуйте: } else if var nestedContainer = try? nestedUnkeyedContainer(), let nestedArray = try? nestedContainer.decode(Array<Any>.self) { - person Jon Brooks; 18.09.2018
comment
Я видел бесконечную рекурсию в массиве, содержащем словарь [{key: value}]. Предложение @JonBrooks исправило это, но создало вложенный массив там, где его не было. Я решил, извлекая массив с помощью nestedUnkeyedContainer в моем инициализаторе Codable: var nc = try unkeyedContainer.nestedUnkeyedContainer() results = try nestedContainer.decode(Array<Any>.self) - person Eli Burke; 01.05.2019

Я тоже поиграл с этой проблемой и, наконец, написал простую библиотеку для работы с типами «универсального 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.

person zoul    schedule 23.09.2017
comment
Это очень элегантное решение. Он чрезвычайно лаконичен, хорошо работает и не является хакерским, как некоторые другие ответы. Моим единственным дополнением было бы поменять число на отдельные типы с плавающей запятой и целые числа. Технически все числа в JS являются числами с плавающей запятой, но более эффективно и чище декодировать целые числа как целые в быстром. - person user3236716; 22.07.2021

Вы можете создать структуру метаданных, которая подтверждает протокол Decodable, и использовать класс JSONDecoder для создания объекта из данных с помощью метода декодирования, как показано ниже.

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)
}
person Suhit Patil    schedule 17.06.2017
comment
Нет, не могу, так как не знаю структуры значения metadata. Это может быть любой произвольный объект. - person Pitiphong Phongpattranont; 17.06.2017
comment
Вы имеете в виду, что это может быть тип массива или словаря? - person Suhit Patil; 18.06.2017
comment
Можете ли вы привести пример или добавить дополнительные пояснения о структуре метаданных - person Suhit Patil; 18.06.2017
comment
Значением metadata может быть любой объект JSON. Так что это может быть пустой словарь или любой другой словарь. метаданные: {} метаданные: {user_id: id} метаданные: {preference: {shows_value: true, language: en}} и т. д. - person Pitiphong Phongpattranont; 18.06.2017
comment
один из возможных вариантов - использовать все параметры в структуре метаданных как необязательные и перечислить все возможные значения в структуре метаданных, например struct metadata {var user_id: String? предпочтение var: Строка? } - person Suhit Patil; 19.06.2017
comment
Если вы знаете структуру полученных данных, то да, это подходящее решение! Очень хорошо! Если он может отличаться, вы можете попробовать пару декодеров, чтобы найти тот, который, надеюсь, сработает. - person David H; 20.04.2018

Я пришел с немного другим решением.

Предположим, у нас есть нечто большее, чем простой [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)
person Giuseppe Lanza    schedule 12.01.2018

Когда я нашел старый ответ, я протестировал только простой объект JSON, но не пустой, что вызовет исключение времени выполнения, такое как @slurmomatic и @zoul found. Извините за эту проблему.

Поэтому я пробую другой способ, используя простой протокол 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, и оказалось, что это очень просто.

я могу сделать

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

в инициализаторе.

Во-первых, мне было плохо по этому поводу.

person Pitiphong Phongpattranont    schedule 21.06.2017
comment
Мог разместить ссылку на вопрос по Apple Developer. Any не соответствует Decodable, поэтому я не уверен, насколько это правильный ответ. - person Reza Shirazian; 21.07.2017
comment
@RezaShirazian Это то, о чем я подумал в первую очередь. Но оказывается, что Dictionary соответствует Encodable, когда его ключи соответствуют Hashable и не зависят от его значений. Вы можете открыть заголовок словаря и убедиться в этом сами. extension Dictionary: Encodable where Key: Hashable extension Dictionary: Decodable where Key: Hashable Forums.developer.apple.com/thread/80288#237680. - person Pitiphong Phongpattranont; 24.07.2017
comment
в настоящее время это не работает. Dictionary ‹String, Any› не соответствует Decodable, потому что Any не соответствует Decodable - person mbuchetics; 24.07.2017
comment
Оказывается, это работает. Я использую это в своем коде. Вы должны понимать, что нет способа выразить требование, чтобы Value of Dictionary соответствовало протоколу Decodable, чтобы словарь теперь соответствовал протоколу Decodable. Это условное соответствие, которое еще не реализовано в Swift 4, я думаю, что на данный момент это нормально, поскольку в системе типов Swift (и универсальных шаблонах) существует множество ограничений. Так что сейчас это работает, но когда система типов Swift улучшится в будущем (особенно, когда будет реализовано условное соответствие), это не должно работать. - person Pitiphong Phongpattranont; 24.07.2017
comment
У меня не работает с Xcode 9 beta 5. Компилируется, но вылетает во время выполнения: Dictionary ‹String, Any› не соответствует Decodable, потому что Any не соответствует Decodable. - person zoul; 11.08.2017
comment
@zoul у меня все еще работает в бета-версии 6 как при компиляции, так и во время выполнения. Кстати, содержимое этого свойства - это просто тип данных JSON. - person Pitiphong Phongpattranont; 23.08.2017
comment
Я только что обнаружил проблему, о которой вы говорили, с пустым JSON, и обновил свое новое решение. Извините за случай, который я пропустил. - person Pitiphong Phongpattranont; 26.08.2017
comment
У меня тоже не работает в финальном выпуске XCODE 9 и swift 4. Я пробовал с [String: AnyObject] .self и [String: Any] .self. Та же ошибка не соответствует Decodable - person harshit2811; 29.09.2017
comment
@ harshit2811Пожалуйста, обратитесь к принятому ответу для правильного обходного пути - person Pitiphong Phongpattranont; 01.10.2017

Если вы используете SwiftyJSON для синтаксического анализа JSON, вы можете обновить его до 4.1.0, который поддерживает Codable протокол. Просто объявите metadata: JSON, и все готово.

import SwiftyJSON

struct Customer {
  let id: String
  let email: String
  let metadata: JSON
}
person allen huang    schedule 26.04.2018
comment
Я не знаю, почему этот ответ был отклонен. Это полностью верно и решает проблему. - person Leonid Usov; 20.12.2018
comment
Вроде бы хорошо для перехода с SwiftyJSON на Decodable - person Michał Ziobro; 25.03.2019
comment
Это не решает, как затем анализировать метаданные json, что было исходной проблемой. - person llamacorn; 21.06.2019

Вы можете взглянуть на 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)!)
person canius    schedule 28.10.2017
comment
Ох, действительно мило. Использование его для получения общего JSON как JToken, добавления некоторых значений и возврата на сервер. Действительно очень хорошо. Вы проделали потрясающую работу :) - person Vitor Hugo Schwaab; 28.02.2018

Я сделал стручок для облегчения способа декодирования + кодирования [String: Any], [Any]. И это обеспечивает кодирование или декодирование дополнительных свойств, здесь https://github.com/levantAJ/AnyCodable

pod 'DynamicCodable', '1.0'

Как это использовать:

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)
    }
}
person Tai Le    schedule 19.01.2019

Вот более общий (не только [String: Any], но и [Any] может декодироваться) и инкапсулированный подход (для этого используется отдельный объект), вдохновленный ответом @loudmouth.

Использование будет выглядеть так:

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 - это вспомогательная сущность, которую мы используем для переноса декодирования данных JSON в объект JSON (массив или словарь) без расширения *DecodingContainer (поэтому он не будет мешать в редких случаях, когда объект JSON не означает [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
    }
  }
}

Обратите внимание, что числовые и логические типы поддерживаются NSNumber, иначе что-то вроде этого не сработает:

if customer.metadata["keyForInt"] as? Int64 { // as it always will be nil
person Alexey Kozhevnikov    schedule 28.11.2017
comment
Могу ли я декодировать только выбранные свойства и оставить другие декодированные автоматически, так как у меня есть 15 свойств, для которых достаточно autoDecoding, и, возможно, 3, для которых требуется некоторая настраиваемая обработка декодирования? - person Michał Ziobro; 20.02.2018
comment
@ MichałZiobro Хотите, чтобы часть данных была декодирована в объект JSON, а часть - в отдельные переменные экземпляра? Или вы спрашиваете о написании инициализатора частичного декодирования только для части объекта (и он не имеет ничего общего с JSON-подобной структурой)? Насколько мне известно, ответ на первый вопрос - да, на второй - нет. - person Alexey Kozhevnikov; 20.02.2018
comment
Я хотел бы иметь только некоторые свойства с настраиваемым декодированием, а остальные со стандартным декодированием по умолчанию - person Michał Ziobro; 20.02.2018
comment
@ MichałZiobro Если я вас правильно понимаю, это невозможно. В любом случае, ваш вопрос не имеет отношения к текущему вопросу SO и заслуживает отдельного рассмотрения. - person Alexey Kozhevnikov; 20.02.2018

декодировать с помощью декодера и ключей кодирования

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) 
}    
person Ashim Dahal    schedule 20.07.2020

Подробности

  • Xcode 12.0.1 (12A7300)
  • Swift 5.3

На основе библиотеки Tai Le

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

Решение

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

использование

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
person Vasily Bodnarchuk    schedule 13.10.2020

Я написал статью и репозиторий, который помогает добавить поддержку [String: Any] для Codable для декодирование, а также кодирование.

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

Это улучшает декодируемый аспект, а также добавляет поддержку кодирования в качестве решения, предоставленного в https://stackoverflow.com/a/46049763/9160905

чего вы сможете добиться:

json

пример кода

person satyen maurya    schedule 02.06.2021

Самый простой и рекомендуемый способ - создать отдельную модель для каждого словаря или модели в формате JSON.

Вот что я делаю

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

    }
}

Использование:

  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 {

        }
    }

** Я использовал optional, чтобы обезопасить себя при синтаксическом разборе, может быть изменен по мере необходимости.

Подробнее по этой теме

person minhazur    schedule 01.01.2018
comment
Ваш ответ наверняка подходит для Swift 4.1, и первая строка вашего сообщения мертва! Предполагая, что данные поступают из веб-службы. вы можете моделировать простые вложенные объекты, а затем использовать точечный синтаксис для каждого из них. См. Ответ Сухита ниже. - person David H; 20.04.2018