my problem is the next, I have a this json
{
"nombre" : "userProfile.user.name!",
"apaterno" : 20,
"amaterno" : true,
"email" : 100,
"start_screen" : {
"info" : true,
"title" : false,
"image" : 20,
"success_btn" : "hola",
"close_btn" : true
}
}
i want to pass this json to my struct, my struct is :
struct person: Decodable
{
var email : Int
var nombre : String
var apaterno : Int
var amaterno: Bool
struct start_screen {
var title: Bool
var info: Bool
var image: Int
var success_btn: String
var close_btn: Bool
}
}
with the next lines I achieved put the json in my struct, but start_screen struct can't get the data.
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
let myStruct = try! decoder.decode(person.self, from: jsonData)
when I access myStruct.email I get 100, its ok, but I can't load the start_screen data, how should I do it?
First you would need to add a variable to person for start_screen.
var start_screen: start_screen
Then you would need to make start_screen Decodable
struct start_screen: Decodable
That should be the minimum amount of changes to get it working.
Additionally, you might want to make your types capitalized. start_screen: start_screen is really confusing looking. You can also make your variable and type names camelCase and have the JSONDecoder convert to/from snake_case for you. It's also the naming convention in swift. It'd look like this
struct Person: Decodable {
var email: Int
var nombre: String
var apaterno: Int
var amaterno: Bool
var startScreen: StartScreen
struct StartScreen: Decodable {
var title: Bool
var info: Bool
var image: Int
var successBtn: String
var closeBtn: Bool
}
}
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let person = try! decoder.decode(Person.self, from: jsonData)
print(person)
This should be your Person struct :
struct Person: Decodable {
var email : Int?
var nombre : String?
var apaterno : Int?
var amaterno: Bool
var start_screen: Start_screen?
enum CodingKeys: String, CodingKey {
case email = "email"
case nombre = "nombre"
case apaterno = "apaterno"
case amaterno = "amaterno"
case start_screen = "amaterno"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(Int.self, forKey: .email)
nombre = try values.decodeIfPresent(String.self, forKey: .nombre)
apaterno = try values.decodeIfPresent(Int.self, forKey: .apaterno)
amaterno = try values.decodeIfPresent(Bool.self, forKey: .apaterno) ?? false
start_screen = try values.decodeIfPresent(Start_screen.self, forKey: .start_screen)
}
}
This should be your Start_screen struct :
struct Start_screen: Decodable {
var title: Bool
var info: Bool
var image: Int?
var success_btn: String?
var close_btn: Bool
enum CodingKeys: String, CodingKey {
case title = "title"
case info = "info"
case image = "image"
case success_btn = "success_btn"
case close_btn = "close_btn"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Bool.self, forKey: .title) ?? false
info = try values.decodeIfPresent(Bool.self, forKey: .info) ?? false
image = try values.decodeIfPresent(Int.self, forKey: .image)
success_btn = try values.decodeIfPresent(String.self, forKey: .success_btn)
close_btn = try values.decodeIfPresent(Bool.self, forKey: .close_btn) ?? false
}
}
Accessing start_screen from Person :
if let jsonData = json.data(using: .utf8) {
let user = try! JSONDecoder().decode(Person.self, from: jsonData)
if let title = user.start_screen.title {
print(title)
}
}
Related
This question already has answers here:
Swift JSOn Decoding Error: Expected to decode Array<Any> but found a dictionary instead
(2 answers)
Closed 1 year ago.
My app must parse JSON data. The software (IceStats) that generates the data, though, generates JSON with two slightly different structures. Sometimes, the "source" value is an array of dictionaries and sometimes it just one dictionary. I can parse the JSON when it is one way or the other, but I don't know how to handle it both ways.
Here is the JSON in the Array version:
{
"icestats": {
"admin": "dontcontactme#localhost",
"host": "server.badradio.biz",
"location": "Airport",
"server_id": "Icecast 2.4.4",
"server_start": "Mon, 26 Apr 2021 12:50:47 -0500",
"server_start_iso8601": "2021-04-26T12:50:47-0500",
"source": [
{
"audio_info": "bitrate=128",
"bitrate": 128,
"genre": "Automation",
"listener_peak": 7,
"listeners": 0,
"listenurl": "http://server.badradio.biz:8000/ambient",
"server_description": "No show is running, tune in for selections from the venerable tape series \"Comfortable & Economical\"",
"server_name": "Comfortable & Economical",
"server_type": "audio/mpeg",
"server_url": "badradio.biz",
"stream_start": "Fri, 30 Apr 2021 06:51:49 -0500",
"stream_start_iso8601": "2021-04-30T06:51:49-0500",
"title": "Vol-15-A",
"dummy": null
},
{
"listeners": 0,
"listenurl": "http://server.badradio.biz:8000/stream",
"dummy": null
}
]
}
}
And here it is in the Dictionary version:
{
"icestats": {
"admin": "dontcontactme#localhost",
"host": "server.badradio.biz",
"location": "Airport",
"server_id": "Icecast 2.4.4",
"server_start": "Mon, 26 Apr 2021 12:50:47 -0500",
"server_start_iso8601": "2021-04-26T12:50:47-0500",
"source": {
"audio_info": "bitrate=128",
"bitrate": 128,
"genre": "Automation",
"listener_peak": 2,
"listeners": 0,
"listenurl": "http://server.badradio.biz:8000/ambient",
"server_description": "No show is running, tune in for selections from the venerable tape series \"Comfortable & Economical\"",
"server_name": "Comfortable & Economical",
"server_type": "audio/mpeg",
"server_url": "badradio.biz",
"stream_start": "Wed, 28 Apr 2021 02:18:31 -0500",
"stream_start_iso8601": "2021-04-28T02:18:31-0500",
"title": "Vol-13-A",
"dummy": null
}
}
}
Finally, here is my data model that handles the Array version:
import Foundation
struct StreamData: Decodable {
let icestats: IceStats
}
struct IceStats: Decodable {
let source: [Source]
}
struct Source: Decodable {
let server_name: String?
let stream_start: String?
let title: String?
let server_description: String?
let server_url: String?
let genre: String?
}
Any help is greatly appreciated. Ideally, I could just change the format of the JSON, but I am not able to.
You can add some custom decoding logic to try both cases:
struct IceStats: Decodable {
var source: [Source]
enum CodingKeys : CodingKey {
case source
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let elements = try? container.decode([Source].self, forKey: .source) {
source = elements
} else if let element = try? container.decode(Source.self, forKey: .source) {
source = [element]
} else {
throw DecodingError.dataCorruptedError(forKey: .source, in: container, debugDescription: "Must be either a single or multiple sources!")
}
}
}
But this can get really long if you also want to decode other properties in IceStats, because you'll have to manually write the decoding code for those too. To avoid this, you can use a property wrapper:
#propertyWrapper
struct MultipleOrSingle<Element: Decodable>: Decodable {
let wrappedValue: [Element]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let elements = try? container.decode([Element].self) {
wrappedValue = elements
} else if let element = try? container.decode(Element.self) {
wrappedValue = [element]
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Container neither contains a single, nor multiple \(Element.self)!")
}
}
}
// Now you can just do this!
struct IceStats: Decodable {
#MultipleOrSingle
var source: [Source]
}
As per your array response your class looks like below and where you get response just decode that response class you will get all data
import Foundation
struct Response : Codable {
let icestats : Icestat?
enum CodingKeys: String, CodingKey {
case icestats = "icestats"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
icestats = Icestat(from: decoder)
}
}
struct Icestat : Codable {
let admin : String?
let host : String?
let location : String?
let serverId : String?
let serverStart : String?
let serverStartIso8601 : String?
let source : [Source]?
enum CodingKeys: String, CodingKey {
case admin = "admin"
case host = "host"
case location = "location"
case serverId = "server_id"
case serverStart = "server_start"
case serverStartIso8601 = "server_start_iso8601"
case source = "source"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
admin = try values.decodeIfPresent(String.self, forKey: .admin)
host = try values.decodeIfPresent(String.self, forKey: .host)
location = try values.decodeIfPresent(String.self, forKey: .location)
serverId = try values.decodeIfPresent(String.self, forKey: .serverId)
serverStart = try values.decodeIfPresent(String.self, forKey: .serverStart)
serverStartIso8601 = try values.decodeIfPresent(String.self, forKey: .serverStartIso8601)
source = try values.decodeIfPresent([Source].self, forKey: .source)
}
}
struct Source : Codable {
let audioInfo : String?
let bitrate : Int?
let dummy : AnyObject?
let genre : String?
let listenerPeak : Int?
let listeners : Int?
let listenurl : String?
let serverDescription : String?
let serverName : String?
let serverType : String?
let serverUrl : String?
let streamStart : String?
let streamStartIso8601 : String?
let title : String?
enum CodingKeys: String, CodingKey {
case audioInfo = "audio_info"
case bitrate = "bitrate"
case dummy = "dummy"
case genre = "genre"
case listenerPeak = "listener_peak"
case listeners = "listeners"
case listenurl = "listenurl"
case serverDescription = "server_description"
case serverName = "server_name"
case serverType = "server_type"
case serverUrl = "server_url"
case streamStart = "stream_start"
case streamStartIso8601 = "stream_start_iso8601"
case title = "title"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
audioInfo = try values.decodeIfPresent(String.self, forKey: .audioInfo)
bitrate = try values.decodeIfPresent(Int.self, forKey: .bitrate)
dummy = try values.decodeIfPresent(AnyObject.self, forKey: .dummy)
genre = try values.decodeIfPresent(String.self, forKey: .genre)
listenerPeak = try values.decodeIfPresent(Int.self, forKey: .listenerPeak)
listeners = try values.decodeIfPresent(Int.self, forKey: .listeners)
listenurl = try values.decodeIfPresent(String.self, forKey: .listenurl)
serverDescription = try values.decodeIfPresent(String.self, forKey: .serverDescription)
serverName = try values.decodeIfPresent(String.self, forKey: .serverName)
serverType = try values.decodeIfPresent(String.self, forKey: .serverType)
serverUrl = try values.decodeIfPresent(String.self, forKey: .serverUrl)
streamStart = try values.decodeIfPresent(String.self, forKey: .streamStart)
streamStartIso8601 = try values.decodeIfPresent(String.self, forKey: .streamStartIso8601)
title = try values.decodeIfPresent(String.self, forKey: .title)
}
}
decode it like
let jsonData = jsonString.data(using: .utf8)!
let response = try! JSONDecoder().decode(Response.self, from: jsonData)
print(response.icestats)
i want import JSON in swift with Codable, modify the object by adding or removing object, and export it in JSON.
Here, my structure
class GenericCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
required init?(stringValue: String) { self.stringValue = stringValue }
required init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
}
class ListSpecie : Codable {
var species: [String : Specie]
required init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: GenericCodingKeys.self)
self.species = [String: Specie]()
for key in container.allKeys{
let value = try container.decodeIfPresent(Specie.self, forKey: GenericCodingKeys(stringValue: key.stringValue)!)
self.species[key.stringValue] = value
}
}
}
class Specie : Codable {
var name : String?
var latinName : [String]?
enum CodingKeys: String, CodingKey {
case name = "l"
case latinName = "ll"
}
required init(from decoder: Decoder) throws
{
let sValues = try decoder.container(keyedBy: CodingKeys.self)
name = try sValues.decodeIfPresent(String.self, forKey: .name)
latinName = try sValues.decodeIfPresent(Array<String>.self, forKey: .latinName)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(name, forKey: .name)
try container.encodeIfPresent(latinName, forKey: .latinName)
}
}
Here, the is a code with sample JSON
let myJson = """
{
"especeID1": {
"l": "Ail",
"ll": ["Allium sativum L.","Allium"]
},
"especeID2": {
"l": "Artichaut",
"ll": ["Cynara cardunculus"]
}
}
"""
let jsonDATA = myJson.data(using: .utf8)!
do{
self.jsonResult = try JSONDecoder().decode(ListSpecie.self, from: jsonDATA)
}catch{
print(error.localizedDescription)
}
Here, I want append or remove specie Object on jsonResult
for myspecie in (self.jsonResult?.species)! {
print(myspecie.key + " " + myspecie.value.name!)
}
// Encodage
let encoder = JSONEncoder()
let productJSON = try! encoder.encode(self.jsonResult?.species)
let jsonString = String(data: productJSON, encoding: .utf8)!
Someone could tell me how i can append or remove a specie object in my jsonResult variable .
Thanks a lot for the help you can bring me.
First of all your code is too complicated, most of the code is redundant.
One class (consider a struct) is sufficient
class Specie : Codable {
var name : String?
var latinName : [String]?
enum CodingKeys: String, CodingKey {
case name = "l"
case latinName = "ll"
}
}
If name and latin name is supposed to appear everywhere declare the properties non-optional (remove the question marks).
And decode the JSON
self.jsonResult = try JSONDecoder().decode([String:Specie].self, from: jsonDATA)
jsonResult is now a dictionary ([String:Specie]), you can remove items
self.jsonResult.removeValue(forKey: "especeID2")
or add an item
let newSpecies = Specie()
newSpecies.name = "Potato"
newSpecies.latinName = ["Solanum tuberosum"]
self.jsonResult["especeID3"] = newSpecies
and encode the object
let encoder = JSONEncoder()
let productJSON = try! encoder.encode(self.jsonResult)
let jsonString = String(data: productJSON, encoding: .utf8)!
Sometimes server sends me property as bool (true, false).
Sometimes server sends me property as an integer (0,1).
How can I decodable this case via standard Decodable in Swift 4?
Example.
I have:
final class MyOffer : Codable {
var id = 0
var pickupAsap: Int?
enum CodingKeys: String, CodingKey {
case id
case pickupAsap = "pickup_asap"
}
}
Responses from server are:
1) "pickup_all_day": true,
2) "pickup_all_day": 0
you may implement your own decode init method, get each class property from decode container, during this section, make your logic dealing with wether "asap" is an Int or Bool, sign all required class properties at last.
here is a simple demo i made:
class Demo: Decodable {
var id = 0
var pickupAsap: Int?
enum CodingKeys: String, CodingKey {
case id
case pickupAsap = "pickup_asap"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(Int.self, forKey: .id)
let pickupAsapBool = try? container.decode(Bool.self, forKey: .pickupAsap)
let pickupAsapInt = try? container.decode(Int.self, forKey: .pickupAsap)
self.pickupAsap = pickupAsapInt ?? (pickupAsapBool! ? 1 : 0)
self.id = id
}
}
mock data:
let jsonInt = """
{"id": 10,
"pickup_asap": 0
}
""".data(using: .utf8)!
let jsonBool = """
{"id": 10,
"pickup_asap": true
}
""".data(using: .utf8)!
test:
let jsonDecoder = JSONDecoder()
let result = try! jsonDecoder.decode(Demo.self, from: jsonInt)
print("asap with Int: \(result.pickupAsap)")
let result2 = try! jsonDecoder.decode(Demo.self, from: jsonBool)
print("asap with Bool: \(result2.pickupAsap)")
output:
asap with Int: Optional(0)
asap with Bool: Optional(1)
for more info: Apple's encoding and decoding doc
I need to parse a json with a game instruction, but in the backend they change the param previously in Int to a Float (IDK why, but now is like that)
this is the error
Unexpected error: dataCorrupted(Swift.DecodingError.Context(codingPath: [ios_apps.HomeResponse.(CodingKeys in _B9BDF2CE76592A42518B3A0888B1E0F5).game, ios_apps.Game.(CodingKeys in _BAE470AF8C81659CF18E4A8D481877B9).counterLeft], debugDescription: "Parsed JSON number <53422.434> does not fit in Int.", underlyingError: nil)).
and this is the struct
import Foundation
struct Game : Decodable {
let gameId : String?
let currentTime : String?
let counterLeft : Int?
let counterStart : String?
let counterStep : String?
let gameEnd : String?
let gameIntroduction : String?
let gamePlay : Bool?
let gameStart : String?
let gameType : String?
let gameUserPlayed : Bool?
let picture : String?
let started : Bool?
// private var counterLeft : Float?
}
struct CounterLeft: Codable, Loopable {
let counterLeft : Int?
enum CodingKeys: String, CodingKey {
case counterLeft = "counterLeft"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
counterLeft = try Int(values.decode(Float.self, forKey: .counterLeft))
}
}
the response form json
game = {
counterLeft = "53422.434";
counterStart = "2018-03-26T07:00:00.000Z";
counterStep = countDown;
currentTime = "2018-03-26T21:09:37.562Z";
gameEnd = "2018-03-28T15:00:51.000Z";
gameId = 5ab906957889034b223b3ba4;
gameIntroduction = "Lorem Ipsum";
gamePlay = 0;
gameStart = "2018-03-27T12:00:00.000Z";
gameType = spinwheel;
gameUserPlayed = 0;
picture = "/files/1522075335538L.png";
started = 1;
};
I need the counterLeft to be in Int for countdown reasons
Use Double. I run a simple model of your test data, it is working.
Here is the code in swift 4:
var json = """
{
"counterLeft" : 53422.434,
"counterStart" : "2018-03-26T07:00:00.000Z"
}
""".data(using: .utf8)
struct model : Codable {
let counterLeft : Int?
let counterStart : String?
enum CodingKeys: String, CodingKey {
case counterLeft = "counterLeft"
case counterStart = "counterStart"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
counterLeft = try Int(values.decodeIfPresent(Double.self, forKey: .counterLeft) ?? 0)
counterStart = try values.decodeIfPresent(String.self, forKey: .counterStart)
}
}
let responseModel = try JSONDecoder().decode(model.self, from: json!)
I am getting data from two different endpoints. One endpoints returns an order like this:
{
"price":"123.49",
"quantity":"4",
"id":"fkuw-4834-njk3-4444",
"highPrice":"200",
"lowPrice":"100"
}
and the other endpoint returns the order like this:
{
"p":"123.49", //price
"q":"4", //quantity
"i":"fkuw-4834-njk3-4444" //id
}
I want to use the same Codable struct to decode both JSON responses. How would I do that? Is it possible to do that using one struct or would I have to create a second struct? Here is what the struct looks like right now for the first JSON return:
struct SimpleOrder:Codable{
var orderPrice:String
var orderQuantity:String
var id:String
var highPrice:String
private enum CodingKeys: String, CodingKey {
case orderPrice = "price"
case orderQuantity = "quantity"
case id
case highPrice
}
}
You can do that but you have to declare all properties as optional and write a custom initializer
struct SimpleOrder : Decodable {
var orderPrice : String?
var orderQuantity : String?
var id : String?
var highPrice : String?
private enum CodingKeys: String, CodingKey {
case orderPrice = "price"
case orderQuantity = "quantity"
case id
case highPrice
case i, q, p
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderPrice = try container.decodeIfPresent(String.self, forKey: .orderPrice)
orderPrice = try container.decodeIfPresent(String.self, forKey: .p)
orderQuantity = try container.decodeIfPresent(String.self, forKey: .orderQuantity)
orderQuantity = try container.decodeIfPresent(String.self, forKey: .q)
id = try container.decodeIfPresent(String.self, forKey: .id)
id = try container.decodeIfPresent(String.self, forKey: .i)
highPrice = try container.decodeIfPresent(String.self, forKey: .highPrice)
}
}
Alternatively use two different key sets, check the occurrence of one of the keys and choose the appropriate key set. The benefit is that price, quantity and id can be declared as non-optional
struct SimpleOrder : Decodable {
var orderPrice : String
var orderQuantity : String
var id : String
var highPrice : String?
private enum CodingKeys: String, CodingKey {
case orderPrice = "price"
case orderQuantity = "quantity"
case id
case highPrice
}
private enum AbbrevKeys: String, CodingKey {
case i, q, p
}
init(from decoder: Decoder) throws {
let cContainer = try decoder.container(keyedBy: CodingKeys.self)
if let price = try cContainer.decodeIfPresent(String.self, forKey: .orderPrice) {
orderPrice = price
orderQuantity = try cContainer.decode(String.self, forKey: .orderQuantity)
id = try cContainer.decode(String.self, forKey: .id)
highPrice = try cContainer.decode(String.self, forKey: .highPrice)
} else {
let aContainer = try decoder.container(keyedBy: AbbrevKeys.self)
orderPrice = try aContainer.decode(String.self, forKey: .p)
orderQuantity = try aContainer.decode(String.self, forKey: .q)
id = try aContainer.decode(String.self, forKey: .i)
}
}
}
There is no need to create a custom initializer for your Codable structure, all you need is to make the properties optional. What I recommend is to create a read only computed property that would return the prices and quantities using a nil coalescing operator so it will always return one or another:
struct Order: Codable {
let price: String?
let quantity: String?
let id: String?
let highPrice: String?
let lowPrice: String?
let p: String?
let q: String?
let i: String?
}
extension Order {
var orderPrice: Double {
return Double(price ?? p ?? "0") ?? 0
}
var orderQuantity: Int {
return Int(quantity ?? q ?? "0") ?? 0
}
var userID: String {
return id ?? i ?? ""
}
}
Testing:
let ep1 = Data("""
{
"price":"123.49",
"quantity":"4",
"id":"fkuw-4834-njk3-4444",
"highPrice":"200",
"lowPrice":"100"
}
""".utf8)
let ep2 = Data("""
{
"p":"123.49",
"q":"4",
"i":"fkuw-4834-njk3-4444"
}
""".utf8)
do {
let order = try JSONDecoder().decode(Order.self, from: ep2)
print(order.orderPrice) // "123.49\n"
print(order.orderQuantity) // "4\n"
print(order.userID) // "fkuw-4834-njk3-4444\n"
} catch {
print(error)
}