I'm trying to Parse dynamic string key this Model
{
"6665": [
"3",
"1",
"",
"3",
"1"
]
}
i set Model like this :
struct PrivaciesResponseModel: Codable {
typealias privacyID = String?
let id : privacyID
let value : [String]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dict = try container.decode([String:[String]].self)
guard let key = dict.keys.first else {
throw NSError(domain: "Decoder", code: 0, userInfo: [:])
}
id = key
value = dict[key] ?? []
}
}
but always response return with empty array
How can i handle this, and what's the Problem?
This is working in my end:
struct DataModel: Decodable {
let data: [String:[String]]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.data = try container.decode([String:[String]].self)
}
}
PS: After getting the data, loop throw key value of the Dictionary to get the dynamic keys.
Related
I'm trying to practice Swift's Codable API.
I send a network request and I receive one single line each time as follows where I have to deal with dynamic keys :
Response example 1:
{
"EUR": 4695.01
}
Response example 2:
{
"USD": 479.01
}
Response example 3:
{
"BTC": 4735.01
}
I tried this method to parse the dynamic keys :
struct ConversionResponseModel: Decodable {
typealias destinationCurrency = String
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
}
}
and my fetching request :
do {
let myResult = try JSONDecoder().decode(ConversionResponseModel.self, from: data)
print(myResult)
} catch {
print(error)
}
But I get this as a result : ConversionResponseModel(), but not the currency values.
It sounds like I'm missing something. Any help please. Thank you
You're almost there. The JSON you're getting will return a dictionary of [String:Double]. You can then covert that using:
struct ConversionResponseModel: Decodable {
typealias DestinationCurrency = String
let currency : DestinationCurrency
let value : Double
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dict = try container.decode([String:Double].self)
guard let key = dict.keys.first else {
throw NSError(domain: "Decoder", code: 0, userInfo: [:])
}
currency = key
value = dict[key] ?? -1
}
}
Note: taking into account Rob Napier's comment, you could substitute Decimal for Double -- see his comment on the original question for more detail
This is my JSON
{
"response":[
[
{
"neededValue" : "Something I really need"
},
567890812309,
"Some text. Bla bla bla",
{
"Garbage" : "Garbage I do not need"
}
]
]
}
It contains an array with two dictionaries, an Int and a String. I need to decode the first dictionary. I tried this:
struct response: Decodable {
var response: [[String : String]]
}
But this obviously doesn't capture the String value and the Int value. How can I describe the array? Please help.
import Foundation
let str = """
{
"response":
[
[
{
"neededValue" : "Something I really need"
},
567890812309,
"Some text. Bla bla bla",
{
"Garbage" : "Garbage I don't need"
}
]
]
}
"""
let data = str.data(using: .utf8)!
struct NeededValue: Decodable {
let neededValue: String
}
struct Garbage: Decodable {
let garbage = ""
enum CodingKeys: String, CodingKey {
case garbage = "Garbage"
}
}
struct Response: Decodable {
let neededValue: NeededValue
let intValue: Int
let stringValue: String
let garbageValue: Garbage
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
neededValue = try container.decode(NeededValue.self)
intValue = try container.decode(Int.self)
stringValue = try container.decode(String.self)
garbageValue = try container.decode(Garbage.self)
}
}
struct Container: Decodable {
let response: [Response]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
response = try container.decode([Response].self, forKey: .response)
}
enum CodingKeys: String, CodingKey {
case response
}
}
do {
let res = try JSONDecoder().decode(Container.self, from: data)
print(res)
} catch {
print(error)
}
BTW, to improve the answer using this link stackoverflow.com/a/47215561/5555803 the better approach is this:
struct Response: Decodable {
var value: Any?
init(from decoder: Decoder) throws {
if let int = try? Int(from: decoder) {
value = int
} else if let string = try? String(from: decoder) {
value = string
} else if let neededValue = try? NeededValue(from: decoder) {
value = neededValue
} else if let garbage = try? Garbage(from: decoder) {
value = garbage
}
}
}
struct ResponseChild: Decodable {
var response: [[Response]]?
}
do {
let res = try JSONDecoder().decode(ResponseChild.self, from: data)
print(res)
} catch {
print(error)
}
I have a simple array
Array[4][
"A",
1,
"A1",
13
]
But how can i parse an JSON array when it contains both int and string? I have no problem converting all the values inside to string if they aren't already, but i can't find any function to do this.
Thanks.
You are getting Array of elements as String or Int which resembles with Array of enum type. So you can parse the underlying Type with the help of an enum.
Have the underlying type structured as:
enum StringOrIntType: Codable {
case string(String)
case int(Int)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(StringOrIntType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
Decoding process:
let jsonData = """
["A", 1, "A1", 13, 15, 2, "B"]
""".data(using: .utf8)!
do {
let decodedArray = try JSONDecoder().decode([StringOrIntType].self, from:jsonData)
// Here, you have your Array
print(decodedArray) // [.string("A"), .int(1), .string("A1"), .int(13), .int(15), .int(2), .string("B")]
// If you want to get elements from this Array, you might do something like below
decodedArray.forEach({ (value) in
if case .string(let integer) = value {
print(integer) // "A", "A1", "B"
}
if case .int(let int) = value {
print(int) // 1, 13, 15, 2
}
})
} catch {
print(error)
}
From the comment on accepted answer: You don't need to worry about the ordering of the items anymore.
Complicated way with Codable
Decode the array with unkeyedContainer
Use a while loop with condition !isAtEnd and decode Int in a do - catch block. If it fails decode String
Easy way with traditional JSONSerialization
Deserialize the object to [CustomStringConvertible] and map the array to [String] with "\($0)"
Edit:
This is an example how to decode the array with Decodable if the items are pairs and the type order is the same:
let jsonArray = """
["A", 1, "A1", 13]
"""
struct Item : Decodable {
var array = [String]()
init(from decoder: Decoder) throws {
var arrayContainer = try decoder.unkeyedContainer()
while !arrayContainer.isAtEnd {
let string = try arrayContainer.decode(String.self)
let int = try arrayContainer.decode(Int.self)
array.append(String(int))
array.append(string)
}
}
}
let data = Data(jsonArray.utf8)
do {
let result = try JSONDecoder().decode(Item.self, from: data)
print(result.array)
} catch { print(error) }
Pasting the JSON sample [1, "1"] into quicktype gives the following Codable implementation:
typealias IntOrStrings = [IntOrString]
enum IntOrString: Codable {
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(IntOrString.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for IntOrString"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
Here is the full source that allows you to do the following:
let items = try IntStrings("[1, \"1\"]")
// Now you have:
// items == [.integer(1), .string("1")]
// so you can map or iterate over this
This is the most typesafe way to represent an int-or-string array from JSON.
I am trying to parse the following JSON using decodable protocol. I am able to parse string value such as roomName. But I am not able to map/parse owners, admins, members keys correctly. For eg, using below code, I can able to parse if the values in owners/members are coming as an array. But in some cases, the response will come as a string value(see owners key in JSON), but I am not able to map string values.
Note: Values of admins, members, owners can be string or array (see owners and members keys in JSON)
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": "anish#local.mac" //This can be array or string
},
"admins": null, //This can be array or string
"members": {
"member": [ //This can be array or string
"steve#local.mac",
"mahe#local.mac"
]
}
}
Model:
struct ChatRoom: Codable{
var roomName: String! = ""
var owners: Owners? = nil
var members: Members? = nil
var admins: Admins? = nil
enum RoomKeys: String, CodingKey {
case roomName
case owners
case members
case admins
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoomKeys.self)
roomName = try container.decode(String.self, forKey: .roomName)
if let member = try? container.decode(Members.self, forKey: .members) {
members = member
}
if let owner = try? container.decode(Owners.self, forKey: .owners) {
owners = owner
}
if let admin = try? container.decode(Admins.self, forKey: .admins) {
admins = admin
}
}
}
//Owner Model
struct Owners:Codable{
var owner: AnyObject?
enum OwnerKeys:String,CodingKey {
case owner
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: OwnerKeys.self)
if let ownerValue = try container.decodeIfPresent([String].self, forKey: .owner){
owner = ownerValue as AnyObject
}
else{
owner = try? container.decode(String.self, forKey: .owner) as AnyObject
}
}
func encode(to encoder: Encoder) throws {
}
}
//Member Model
struct Members:Codable{
var member:AnyObject?
enum MemberKeys:String,CodingKey {
case member
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MemberKeys.self)
if let memberValue = try container.decodeIfPresent([String].self, forKey: .member){
member = memberValue as AnyObject
}
else{
member = try? container.decode(String.self, forKey: .member) as AnyObject
}
}
func encode(to encoder: Encoder) throws {
}
}
This should work. I've removed Admin model for simplicity. I'd prefer Owners/Members to be arrays as they can have one or more values which is what they're for, but if you want them to be AnyObject, you can cast them as so like you're already doing in your init(decoder:).
Test data:
var json = """
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": "anish#local.mac"
},
"admins": null,
"members": {
"member": [
"steve#local.mac",
"mahe#local.mac"
]
}
}
""".data(using: .utf8)
Models:
struct ChatRoom: Codable, CustomStringConvertible {
var roomName: String! = ""
var owners: Owners? = nil
var members: Members? = nil
var description: String {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try? encoder.encode(self)
return String(data: data!, encoding: .utf8)!
}
enum RoomKeys: String, CodingKey {
case roomName
case owners
case members
case admins
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoomKeys.self)
roomName = try container.decode(String.self, forKey: .roomName)
members = try container.decode(Members.self, forKey: .members)
owners = try? container.decode(Owners.self, forKey: .owners)
}
}
struct Owners:Codable{
var owner: [String]?
enum OwnerKeys:String,CodingKey {
case owner
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: OwnerKeys.self)
if let ownerValue = try? container.decode([String].self, forKey: .owner){
owner = ownerValue
}
else if let own = try? container.decode(String.self, forKey: .owner) {
owner = [own]
}
}
}
struct Members: Codable {
var member:[String]?
enum MemberKeys:String,CodingKey {
case member
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MemberKeys.self)
if let memberValue = try? container.decode([String].self, forKey: .member){
member = memberValue
}
else if let str = try? container.decode(String.self, forKey: .member){
member = [str]
}
}
}
Test:
var decoder = JSONDecoder()
try? print("\(decoder.decode(ChatRoom.self, from: json!))")
Output:
{
"owners" : {
"owner" : [
"anish#local.mac"
]
},
"members" : {
"member" : [
"steve#local.mac",
"mahe#local.mac"
]
},
"roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}
As you are getting some data as Array or String you can parse this underlying Type with the help of an enum. This will reduce some boilerplate codes as well as redundant codes for each Type you define that is able to have Array or String values.
You define an enum like this:
enum ArrayOrStringType: Codable {
case array([String])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .array(container.decode([String].self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(ArrayOrStringType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .array(let array):
try container.encode(array)
case .string(let string):
try container.encode(string)
}
}
}
And then your models go as:
struct ChatRoom: Codable {
let roomName: String
let owners: Owner
let admins: ArrayOrStringType? // as you are likely to get null values also
let members: Member
struct Owner: Codable {
let owner: ArrayOrStringType
}
struct Member: Codable {
let member: ArrayOrStringType
}
}
/// See!! No more customization inside every init(from:)
Now you can parse your data that contains any of your desired type (Array, String)
Test data 1:
// owner having String type
let jsonTestData1 = """
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": "anish#local.mac"
},
"admins": null,
"members": {
"member": [
"steve#local.mac",
"mahe#local.mac"
]
}
}
""".data(using: .utf8)!
Test data 2:
// owner having [String] type
let jsonTestData2 = """
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": ["anish1#local.mac", "anish2#local.mac"]
},
"admins": null,
"members": {
"member": [
"steve#local.mac",
"mahe#local.mac"
]
}
}
""".data(using: .utf8)!
Decoding process:
do {
let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:jsonTestData1)
print(chatRoom)
} catch {
print(error)
}
// will print
{
"owners" : {
"owner" : "anish#local.mac"
},
"members" : {
"member" : [
"steve#local.mac",
"mahe#local.mac"
]
},
"roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}
do {
let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:jsonTestData2)
print(chatRoom)
} catch {
print(error)
}
// will print
{
"owners" : {
"owner" : [
"anish1#local.mac",
"anish2#local.mac"
]
},
"members" : {
"member" : [
"steve#local.mac",
"mahe#local.mac"
]
},
"roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}
You can even get more out of the structure. Lets say, you want to work with owners only. You will likely try to get the values as Swifty way:
do {
let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:json)
if case .array(let owners) = chatRoom.owners.owner {
print(owners) // ["anish1#local.mac", "anish2#local.mac"]
}
if case .string(let owners) = chatRoom.owners.owner {
print(owners) // "anish#local.mac"
}
} catch {
print(error)
}
Hope this structuring helps a lot more than other typical ways. Plus this is having the explicit consideration of your expected types. Neither it relies on one type (Array only) nor Any/AnyObject type.
I recreated yours models and tested with your JSON and it worked fine. If your backend returns different types in the different cases (business rules), maybe the best way is create separate variables for each case.(imho)
// Model
import Foundation
struct ChatRoom : Codable {
let roomName : String?
let owners : Owners?
let admins : String?
let members : Members?
enum CodingKeys: String, CodingKey {
case roomName = "roomName"
case owners
case admins = "admins"
case members
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
roomName = try values.decodeIfPresent(String.self, forKey: .roomName)
owners = try Owners(from: decoder)
admins = try values.decodeIfPresent(String.self, forKey: .admins)
members = try Members(from: decoder)
}
}
-
// Member Model
import Foundation
struct Members : Codable {
let member : [String]?
enum CodingKeys: String, CodingKey {
case member = "member"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
member = try values.decodeIfPresent([String].self, forKey: .member)
}
}
-
// Owner Model
import Foundation
struct Owners : Codable {
let owner : String?
enum CodingKeys: String, CodingKey {
case owner = "owner"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
owner = try values.decodeIfPresent(String.self, forKey: .owner)
}
}
I am using Swift 4 and trying to parse some JSON data which apparently in some cases can have different type values for the same key, e.g.:
{
"type": 0.0
}
and
{
"type": "12.44591406"
}
I am actually stuck with defining my struct because I cannot figure out how to handle this case because
struct ItemRaw: Codable {
let parentType: String
enum CodingKeys: String, CodingKey {
case parentType = "type"
}
}
throws "Expected to decode String but found a number instead.", and naturally,
struct ItemRaw: Codable {
let parentType: Float
enum CodingKeys: String, CodingKey {
case parentType = "type"
}
}
throws "Expected to decode Float but found a string/data instead." accordingly.
How can I handle this (and similar) cases when defining my struct?
I ran into the same issue when trying to decode/encode the "edited" field on a Reddit Listing JSON response. I created a struct that represents the dynamic type that could exist for the given key. The key can have either a boolean or an integer.
{ "edited": false }
{ "edited": 123456 }
If you only need to be able to decode, just implement init(from:). If you need to go both ways, you will need to implement encode(to:) function.
struct Edited: Codable {
let isEdited: Bool
let editedTime: Int
// Where we determine what type the value is
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Check for a boolean
do {
isEdited = try container.decode(Bool.self)
editedTime = 0
} catch {
// Check for an integer
editedTime = try container.decode(Int.self)
isEdited = true
}
}
// We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try isEdited ? container.encode(editedTime) : container.encode(false)
}
}
Inside my Codable class, I then use my struct.
struct Listing: Codable {
let edited: Edited
}
Edit: A more specific solution for your scenario
I recommend using the CodingKey protocol and an enum to store all the properties when decoding. When you create something that conforms to Codable the compiler will create a private enum CodingKeys for you. This lets you decide on what to do based on the JSON Object property key.
Just for example, this is the JSON I am decoding:
{"type": "1.234"}
{"type": 1.234}
If you want to cast from a String to a Double because you only want the double value, just decode the string and then create a double from it. (This is what Itai Ferber is doing, you would then have to decode all properties as well using try decoder.decode(type:forKey:))
struct JSONObjectCasted: Codable {
let type: Double?
init(from decoder: Decoder) throws {
// Decode all fields and store them
let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum.
// First check for a Double
do {
type = try container.decode(Double.self, forKey: .type)
} catch {
// The check for a String and then cast it, this will throw if decoding fails
if let typeValue = Double(try container.decode(String.self, forKey: .type)) {
type = typeValue
} else {
// You may want to throw here if you don't want to default the value(in the case that it you can't have an optional).
type = nil
}
}
// Perform other decoding for other properties.
}
}
If you need to store the type along with the value, you can use an enum that conforms to Codable instead of the struct. You could then just use a switch statement with the "type" property of JSONObjectCustomEnum and perform actions based upon the case.
struct JSONObjectCustomEnum: Codable {
let type: DynamicJSONProperty
}
// Where I can represent all the types that the JSON property can be.
enum DynamicJSONProperty: Codable {
case double(Double)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Decode the double
do {
let doubleVal = try container.decode(Double.self)
self = .double(doubleVal)
} catch DecodingError.typeMismatch {
// Decode the string
let stringVal = try container.decode(String.self)
self = .string(stringVal)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
One simple solution is to provide an implementation of init(from:) which attempts to decode the value as a String, and if that fails because the type is wrong, attempt to decode as a Double:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.parentType = try container.decode(String.self, forKey: .parentType)
} catch DecodingError.typeMismatch {
let value = try container.decode(Double.self, forKey: .parentType)
self.parentType = "\(value)"
}
}
I had to decode PHP/MySQL/PDO double value that is given as an String, for this use-case I had to extend the KeyedDecodingContainer, like so:
extension KeyedDecodingContainer {
func decode(forKey key: KeyedDecodingContainer.Key) throws -> Double {
do {
let str = try self.decode(String.self, forKey: key)
if let dbl = Double(str) {
return dbl
}
} catch DecodingError.typeMismatch {
return try self.decode(Double.self, forKey: key)
}
let context = DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Wrong Money Value")
throw DecodingError.typeMismatch(Double.self, context)
}
}
Usage:
let data = """
{"value":"1.2"}
""".data(using: .utf8)!
struct Test: Decodable {
let value: Double
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
self.value = try decoder.container(keyedBy: CodingKeys.self)
.decode(forKey: CodingKeys.value)
}
}
try JSONDecoder().decode(Test.self, from: data).value
// Out Put Json
{
"software_id": "10",
"name": "Kroll"
},
{
"software_id": 580,
"name": "Synmed"
}
// Codable Struct
struct SoftwareDataModel: Codable {
var softwareId:MyValue?
var name:String?
enum CodingKeys: String, CodingKey{
case softwareId = "software_id"
case name
}
}
MYValue is Codable Struct Which help to to convert your datatype into "String" here I mentions only String and Int datatypes.
enum MyValue: Codable {
case string(String)
var stringValue: String? {
switch self {
case .string(let s):
return s
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(Int.self) {
self = .string("\(x)")
return
}
throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let x):
try container.encode(x)
}
}
}
// How to get software_id ?
let softwareId = Struct_object.softwareId?.stringValue ?? "0"