How to make a struct with `variable: [String: Codable]` codable? - swift

I got a struct like the following
struct Wrapper {
var value: [String: Any]
// type "Any" could be String, Int or [String].
// i.g. ["a": 1, "b": ["ccc"]]
// and the keys of this dictionary are not determined
}
I been struggled for quite a while😭.
Anyone has any idea how to resolve it?

You can use some library like AnyCodable
Then you can make your struct Codable by using AnyCodable class instead of Any.
struct Wrapper: Codable {
var value: [String: AnyCodable]
}
Example
let arrayWrapper: [String: Any] =
["value" :
[
"test" : ["1", "2", "3"],
"parse" : ["4", "5", "6"]]
]
let jsonData = try! JSONSerialization.data(withJSONObject: arrayWrapper, options: .prettyPrinted)
do {
let decoder = JSONDecoder()
let result = try decoder.decode(Wrapper.self, from: jsonData)
print("result:", result)
} catch let error {
print("error:", error)
}
Output
result: Wrapper(value: ["parse": AnyCodable(["4", "5", "6"]), "test": AnyCodable(["1", "2", "3"])])

Related

Swift: convert [Dictionary<String, [String : Double]>.Element] to [String : [String : Double]]

I have this JSON string:
[
{
"name": "first_category",
"model": {
"OK": 0.49404761904761907,
"NOPE": 0.48214285714285715
}
},
{
"name": "second_category",
"model": {
"YOP": 0.389338593,
"GO": 0.20420894
}
}
]
I have created this struct to decode it:
struct JSONModel: Codable {
let name: String
let model: [String: Double]
}
The decoding:
let decodedModel = try? decoder.decode([JSONModel].self, from: Data(jsonString.utf8))
It correctly fills the array as expected, but now I would like to use this array to create a dictionary whose keys would be the JSONModel names, and values the models. This is what I expect to get:
[
"first_category": [
"OK": 0.49404761904761907,
"NOPE": 0.4821428571428571
],
"second_category": [
"YOP": 0.389338593,
"GO": 0.20420894
]
]
So I tried this:
let simplifiedModel: [String: [String: Double]] = decodedModel.flatMap { [$0.name: $0.model] }
But I'm getting this error:
Cannot convert value of type '[Dictionary<String, [String : Double]>.Element]' to closure result type '[String : [String : Double]]'
What should I do instead?
Thank you for your help
I would use reduce(into:) for this
let dictionary = decodedModel.reduce(into: [:]) {
$0[$1.name] = $1.model
}

JsonDecoder decode array with same key comes as different datatype in swift

let responseData = try JSONDecoder().decode(MenuResponseModel, from: jsonData!)
struct MenuResponseModel : Codable {
let ratingAverage : Double?
let ratingcount : Int?
}
Sample Response:
[{
"ratingAverage": 3,
"reviewCount": 100,
},{
"ratingAverage": 4.2,
"reviewCount": 10,
}]
ratingAverage ==> Some times it's coming as "Double" or "Int"
Can some one suggested me, how can I do that by using Codable approach?
As I said in the comments Double considers both Int and floating point values.
There are two major issues:
You have to decode an array [MenuResponseModel]
The key reviewCount doesn't match the struct member ratingcount
This decodes the JSON correctly
let jsonString = """
[{"ratingAverage": 3,"reviewCount": 100},
{"ratingAverage": 4.2,"reviewCount": 10}
]
"""
struct MenuResponseModel : Codable {
let ratingAverage : Double
let reviewCount : Int
}
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode([MenuResponseModel].self, from: data)
print(result)
} catch {
print(error)
}

CREATE JSON SWIFTYJSON

I am trying to create a json like this
[
{"key": "value"},
{"key": "value"},
{"key": "value"}
]
this is my code
var array: JSON = []
for i in (0..<obj.count){
array = [
"user": obj[i].name!,
"apikey": obj[i].key!
]
}
But when I try to print, only I get with the first item,
How can I resolve that
Use Codable. Since Swift 4 SwiftyJSON became obsolete
struct Object : Encodable {
private enum CodingKeys : String, CodingKey { case name = "user", key = "apikey" }
let name, key : String
}
let obj = [Object(name: "Foo", key: "API123"), Object(name: "Bar", key: "API456")]
do {
let jsonData = try JSONEncoder().encode(obj)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
} catch { print(error) }

Swift Codable: decode dictionary with unknown keys

Codable is great when you know the key formatting of the JSON data. But what if you don't know the keys? I'm currently faced with this problem.
Normally I would expect JSON data to be returned like this:
{
"id": "<123>",
"data": [
{
"id": "<id1>",
"event": "<event_type>",
"date": "<date>"
},
{
"id": "<id2>",
"event": "<event_type>",
"date": "<date>"
},
]
}
But this is what I'm aiming to decode:
{
"id": "123",
"data": [
{ "<id1>": { "<event>": "<date>" } },
{ "<id2>": { "<event>": "<date>" } },
]
}
Question is: how do I use Codable to decode JSON where the keys are unique? I feel like I'm missing something obvious.
This is what I'm hoping to do so I can use Codable:
struct SampleModel: Codable {
let id: String
let data: [[String: [String: Any]]]
// MARK: - Decoding
enum CodingKeys: String, CodingKey {
case id = "id"
case data = "data"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
// This throws an error: Ambiguous reference to member 'decode(_:forKey:)'
data = try container.decode([[String: [String: Any]]].self, forKey: .data)
}
}
This throws an error: Ambiguous reference to member 'decode(_:forKey:)'
For your completely changed question, the solution is very similar. Your struct simply adds one additional layer above the array. There's no need for any custom decoding nor even any CodingKeys.
Note that you can't use Any in a Codable.
let json="""
{
"id": "123",
"data": [
{ "<id1>": { "<event>": "2019-05-21T16:15:34-0400" } },
{ "<id2>": { "<event>": "2019-07-01T12:15:34-0400" } },
]
}
"""
struct SampleModel: Codable {
let id: String
let data: [[String: [String: Date]]]
}
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode(SampleModel.self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}
The original answer for your original question.
Since you have an array of nested dictionary where none of the dictionary keys are fixed, and since there are no other fields, you can just decode this as a plain array.
Here's an example:
let json="""
[
{ "<id1>": { "<event>": "2019-07-01T12:15:34-0400" } },
{ "<id2>": { "<event>": "2019-05-21T17:15:34-0400" } },
]
"""
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode([[String: [String: Date]]].self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}

Swift Codable with Different Array Types

I'm writing a program where I'm parsing JSON data that includes array of arrays, where the nested arrays have different object types (specifically, [[String, String, Int]]). For example,
{
"number": 5295,
"bets": [
[
"16",
"83",
9
],
[
"75",
"99",
4
],
[
"46",
"27",
5
]
]
}
I'm trying to use codable to help me parse the data, but when I try something like
struct OrderBook: Codable {
let number: Int
let bets: [Bet]
}
struct Bet: Codable {
let price: String
let sale: String
let quantity: Int
}
it gives me errors saying that
Expected to decode Dictionary<String, Any> but found an array instead
How do I get around this? I can't declare an array of empty type.
One solution (assuming you can't change the JSON) is to implement custom decoding logic for Bet. You can use an unkeyed container (which reads from a JSON array) in order to decode each of the properties in turn (the order in which you call decode(_:) is the order they're expected to appear in the array).
import Foundation
struct OrderBook : Codable {
let number: Int
let bets: [Bet]
}
struct Bet : Codable {
let price: String
let sale: String
let quantity: Int
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
self.price = try container.decode(String.self)
self.sale = try container.decode(String.self)
self.quantity = try container.decode(Int.self)
}
// if you need encoding (if not, make Bet Decodable
// and remove this method)
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(price)
try container.encode(sale)
try container.encode(quantity)
}
}
Example decoding:
let jsonString = """
{ "number": 5295, "bets": [["16","83",9], ["75","99",4], ["46","27",5]] }
"""
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(OrderBook.self, from: jsonData)
print(decoded)
} catch {
print(error)
}
// OrderBook(number: 5295, bets: [
// Bet(price: "16", sale: "83", quantity: 9),
// Bet(price: "75", sale: "99", quantity: 4),
// Bet(price: "46", sale: "27", quantity: 5)
// ])