Swift - Encode and Decode a Dictionary [String:Any] into Plist

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



Leave a reply



Submit