Swift - Encode and Decode a dictionary [String:Any] into plist
So finally worked it out with the help of Andrada.
I added a second struct which held the action and by passed having to use [string:any]
class Marker : Encodable, Decodable {
var UUIDpic: UUID = UUID()
var alpha: Int = 1
var buttonType: Int = 0
var buttonAction : [String: [ButtonAction]] = [:] //Dictionary I edited using the new struct
var buttonNameColor: String = ""
var buttonNameFontSize: Int = 10
var buttonShape: String = ""
var loggerRect: String = ""
var maskColor: String = ""
var name: String = ""
}
Below is the struct I added
struct ButtonAction: Codable {
var action: String
var array_linked_of_buttons: [[String:String]]
init(action: String, array_linked_of_buttons: [[String:String]]) {
self.action = action
self.array_linked_of_buttons = array_linked_of_buttons
}
}
Make sure to init your struct or it won't work.
How to decode a property with type of JSON dictionary in Swift [45] decodable protocol
With some inspiration from this gist I found, I wrote some extensions for UnkeyedDecodingContainer
and KeyedDecodingContainer
. 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:
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. 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.
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)
}
}
Codable [String: Any] dictionary
Is it possible to use Codable with [String: Any] dictionary?
no, you can't with Codable but you can make this dictionary in a new model.
Replace
public var message: [String: Any]?
with
public var message: MessageModel?
struct MessageModel: Codable { }
How to directly convert a Dictionary to a Codable instance in Swift?
It looks like you have JSON that looks like this:
let r1 = Data("""
{"code": "0", "info": {"user_id": 123456}}
""".utf8)
let r2 = Data("""
{"code": "0", "info": {"temperature": 20, "country": "London"}}
""".utf8)
And "info" types that look like this:
struct UserID: Codable {
var userId: Int
}
struct WeatherTemperature: Codable {
var temperature: Int
var country: String
}
I'm assuming a decoder that does the snakeCase conversion (you can replace this by implementing CodingKeys or whatever):
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Given that, you need a generic Response type:
struct Response<Info: Codable>: Codable {
var code: String
var info: Info
}
And with that, you can decode your response directly from the JSON object:
let userID = try decoder.decode(Response<UserID>.self, from: r1).info
let weather = try decoder.decode(Response<WeatherTemperature>.self, from: r2).info
Modern way of CMTime and CMTimeRange in plist using Swift
Use PropertyListEncoder
/PropertyListDecoder
with a Codable
model type. CMTime
and CMTimeRange
are not Codable
conformant by default, so you need to add the conformance yourself.
extension CMTime: Codable {
enum CodingKeys: String, CodingKey {
case value
case timescale
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let value = try container.decode(CMTimeValue.self, forKey: .value)
let timescale = try container.decode(CMTimeScale.self, forKey: .timescale)
self.init(value: value, timescale: timescale)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: .value)
try container.encode(timescale, forKey: .timescale)
}
}
extension CMTimeRange: Codable {
enum CodingKeys: String, CodingKey {
case start
case duration
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let start = try container.decode(CMTime.self, forKey: .start)
let duration = try container.decode(CMTime.self, forKey: .duration)
self.init(start: start, duration: duration)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(start, forKey: .start)
try container.encode(duration, forKey: .duration)
}
}
struct Model: Codable {
let time: CMTime
let timeRange: CMTimeRange
}
let model = Model(time: CMTime.invalid, timeRange: CMTimeRange(start: CMTime(), end: CMTime()))
do {
let encodedData = try PropertyListEncoder().encode(model)
let decodedData = try PropertyListDecoder().decode(Model.self, from: encodedData)
print(decodedData)
} catch {
print(error)
}
Decode Nested Dictionaries in Plist
You can't iterate through a dictionary the same way as you iterate through an array.
You will have to change
for dicts in admin {
to
for (key, value) in admin {
as a dictionary-entry consists of a key and a value and not just a single object like e.g. an array.
If you want to iterate through all dictionaries you will "need" recursion. If you don't know what that is, it's basically a method that calls itself (but not infinitely).
You could do that for example like this:
func iterateThroughDictionary(dict: Dictionary<String, Any>) {
for (key, value) in dict {
if let subDict = value as? Dictionary<String, Any> {
iterateThroughDictionary(dict: subDict)
} else {
print(value);
}
}
}
Then you just have to call it with the root-dictionary.
Related Topics
Charts Not Plotting in Tableviewcell
Comparing Two Enum Variables Regardless of Their Associated Values
Using "Codable" to Set Property Values Doesn't Work Through Inheritance
With Swiftui, How to Constrain a View's Size to Another Non-Sibling View
How to Get Rid of Array Brackets While Printing
Limiting Concurrent Access to a Service Class with Rxswift
How to Encode and Decode the Closures
Swift 3 Issue with Cvararg Being Passed Multiple Times
How to Access Modifiers of a View in Swiftui
Need Explanation About Random Function Swift
Swift Generics: Non-Nominal Type Does Not Support Explicit Initialization
Dynamic Datasource/Delegates for Uitableview in Swift
Convert [(Key: String, Value: String)] in [String:String]
Why There Is a Overflow with Swift Language When Assign a 8 Bits Binary Value to a Var of Int8 Type