I'm making an api call to get some data, the json dataAsString looks like this...
guard let dataAsString = String(data: data, encoding: .utf8)else {return}
print(dataAsString)
JSON DATA
{"patch_report":{"name":"macOS Updates","patch_software_title_id":"1","total_computers":"5","total_versions":"1","versions":{"version":{"software_version":"10.15.3","computers":{"size":"5","computer":{"id":"467","name":"EPART1BGF8J9"}}}}}}
do {
// make sure this JSON is in the format we expect
if let json = try JSONSerialization.jsonObject(with:data, options:[]) as? [String: Any] {
// try to read out a string array
if let patch_report = json["patch_report"] as? [String] {
print(patch_report)
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
How could I use JSONSerialization to get only certain nested JSON values?
I've been told by many not to use JSONSerialization so I've provided an alternate solution. Below has the Codable solution as well.
Click Here For An Explination - Using Codable with nested JSON!
I was able to find the answers below...
Get nested JSON using JSONSerialization
do {
// make sure this JSON is in the format we expect
if let json = try JSONSerialization.jsonObject(with:data, options:[]) as? [String: Any] {
// try to read out a string array
if let patch_report = json["patch_report"] as? [String:Any] {
if let versions = patch_report["versions"] as? [String:Any] {
if let version = versions["version"] as? [String:Any] {
if let software_version = version["software_version"] as? String {
print(software_version)
}
}}
} else {print("Not Available")}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
Get nested Json using Decoder
struct PatchReport: Codable {
var patch_report:Patch_report?
enum CodingKeys: String, CodingKey{
case patch_report = "patch_report"
}
struct Patch_report: Codable {
var name:String?
var patch_software_title_id:String?
var total_computers:String?
var total_versions:String?
var versions: Versions?
enum CodingKeys: String, CodingKey {
case name = "name"
case patch_software_title_id = "patch_software_title_id"
case total_computers = "total_computers"
case total_versions = "total_versions"
case versions = "versions"
}
struct Versions: Codable {
var version: Version?
enum CodingKeys: String, CodingKey {
case version = "version"
}
struct Version: Codable {
var software_version:String?
var computers:Computers?
enum CodingKeys: String, CodingKey {
case software_version = "software_version"
case computers = "computers"
}
struct Computers: Codable{
var size:String?
var computer:Computer?
enum CodingKeys: String, CodingKey {
case size = "size"
case computer = "computer"
}
struct Computer: Codable{
var id:String?
var name:String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
}
}
}
}
}
}
let response = try! JSONDecoder().decode(PatchReport.self, from: data)
print(String(response.patch_report?.total_computers ?? ""))
You're trying to cast patch_report as a String which won't work. Try this instead:
if let patch_report = json["patch_report"] as? [String: Any] {
print(patch_report["name"])
}
Related
I parsed data from a json file, but I don't know how to get these variables.
Need charcode, name and value.
I need to display them in a table using swiftui. I got a mess in the console and I don't know how to get to this data
this is struct
import Foundation
struct CurrencyModel: Codable {
let valute: [String : Valute]
enum CodingKeys: String, CodingKey {
case valute = "Valute"
}
}
struct Valute: Codable {
let charCode, name: String
let value: Double
enum CodingKeys: String, CodingKey {
case charCode = "CharCode"
case name = "Name"
case value = "Value"
}
}
and this is parser
class FetchDataVM: ObservableObject {
var valueData = [String : Any]()
init() {
fetchCurrency()
}
func fetchCurrency() {
let urlString = "https://www.cbr-xml-daily.ru/daily_json.js"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) {data, _, error in
DispatchQueue.main.async {
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(CurrencyModel.self, from: data)
print(decodedData)
} catch {
print("Error! Something went wrong.")
}
}
}
}.resume()
}
}
As all needed information is in the Valute struct you need only the values of the valute dictionary. Replace
var valueData = [String : Any]()
with
#Published var valueData = [Valute]()
and after the line print(decodedData) insert
self.valueData = decodedData.valute.values.sorted{$0.name < $1.name}
or
self.valueData = decodedData.valute.values.sorted{$0.charCode < $1.charCode}
In the view you can iterate the array simply with a ForEach expression
I am trying to decode this type of JSON-Data in Swift
{"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}
The name and number of ingredients is variable. Therefore I am only able to decode it in this type of structure:
struct Product: Codable {
var total_ingredients: [[String: String]]?
private enum CodingKeys : String, CodingKey {
case total_ingredients = "Total ingredients"
}
}
But I would like to be able to decode it in either one dictionary: var total_ingredients: [String: String]? or my preferred choice in an array of objects: var total_ingredients: [Ingredient]?
struct Ingredient: Codable {
var name: String
var percentage: String
}
I already tried to solve my problem with an extension but it isn't working and I don't think that's the correct approach:
extension Ingredient {
init(_ ingredient: [String: String]) {
var key: String = ""
var value: String = ""
for data in ingredient {
key = data.key
value = data.value
}
self = .init(name: key, percentage: value)
}
}
Thanks in advance :)
You have to implement init(from decoder and map the array of dictionaries to Ingredient instances
struct Product: Decodable {
let totalIngredients: [Ingredient]
private enum CodingKeys : String, CodingKey { case totalIngredients = "Total ingredients" }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let ingredientData = try container.decode([[String:String]].self, forKey: .totalIngredients)
totalIngredients = ingredientData.compactMap({ dict -> Ingredient? in
guard let key = dict.keys.first, let value = dict[key] else { return nil }
return Ingredient(name: key, percentage: value)
})
}
}
struct Ingredient {
let name, percentage: String
}
let jsonString = """
{"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Product.self, from: data)
print(result)
} catch {
print(error)
}
The extension is not needed.
I'm having trouble accessing the StatusList array from this API response. How would I get that information?
my current code is and does not work.
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
for list in (parsedData["StatusList"] as? [String])!{
for shipmentstatus in list["Details"]{
//doesn't work
}
}
here is the JSON
{
"MobileAPIError":"",
"StatusList":{
"ErrorMessage":"",
"Details":[
{
"Pro":"000000000",
"BlNumber":"000000",
"ReferenceNumber":"",
"Scac":"CNWY",
"Carrier":"XPO LOGISTICS FREIGHT, INC.",
"ShipperCode":"xx999",
"ShipperName":"W B EQUIPMENT",
"ShipperCity":"WOOD RIDGE",
"ShipperState":"NJ"
},
{
"Pro":"0000000",
"BlNumber":"#00000-R",
"ReferenceNumber":"",
"Scac":"CNWY",
"Carrier":"XPO LOGISTICS FREIGHT, INC.",
"ShipperCode":"xx999",
"ShipperName":"W B EQUIPMENT",
"ShipperCity":"WOOD RIDGE",
"ShipperState":"NJ"
},
]
}
}
EDIT: I would like to try to use JSONDecoder, as it looks like a decent solution.
Would this work?
struct ShipmentStatusList: Decodable {
let MobileAPIError: String
let StatusList: StatusListItems
enum CodingKeys : String, CodingKey {
case MobileAPIError
case StatusList
}
}
struct StatusListItems{
let ErrorMessage: String
let Details: [Details]
}
struct Details {
let Pro: String
let BLNumber: String
let ReferenceNumber: String
}
The value for key StatusList is a dictionary, please note the {}, the array is the value for key Details in statusList
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any],
let statusList = parsedData["StatusList"] as? [String:Any],
let details = statusList["Details"] as? [[String:Any]] {
for detail in details {
print(detail["Pro"])
}
}
}
And don't do things like (... as? ...)!, never!
The corresponding Codable structs are
struct Status: Codable {
let mobileAPIError: String
let statusList: StatusList
enum CodingKeys: String, CodingKey { case mobileAPIError = "MobileAPIError", statusList = "StatusList" }
}
struct StatusList: Codable {
let errorMessage: String
let details: [Detail]
enum CodingKeys: String, CodingKey { case errorMessage = "ErrorMessage", details = "Details" }
}
struct Detail: Codable {
let pro, blNumber, referenceNumber, scac: String
let carrier, shipperCode, shipperName, shipperCity: String
let shipperState: String
enum CodingKeys: String, CodingKey {
case pro = "Pro", blNumber = "BlNumber", referenceNumber = "ReferenceNumber"
case scac = "Scac", carrier = "Carrier", shipperCode = "ShipperCode"
case shipperName = "ShipperName", shipperCity = "ShipperCity", shipperState = "ShipperState"
}
}
do {
let result = try JSONDecoder().decode(Status.self, from: data!)
print(result)
} catch { print(error) }
I got to know struct "Codable" in swift 4.0, *.
So, I tried that when decode josn.
if let jsonData = jsonString.data(using: .utf8) {
let decodingData = try? JSONDecoder().decode(SampleModel.self, from: jsonData)
}
Example sample data model below.
struct SampleModel : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
And sample json data is .. below.
{
"data": {
"result" : 1
"total_count": 523,
"list": [
{
"no": 16398,
"category" : 23,
"template_seq" : 1
},
{
"no": -1,
"category" : 23,
"template_seq" : 1
}
]
}
}
But i want filtering wrong data.
If the value of "no" is less than or equal to 0, it is an invalid value.
Before not using codable...below.
(using Alamifre ison response )
guard let dictionaryData = responseJSON as? [String : Any] else { return nil }
guard let resultCode = dictionaryData["result"] as? Bool , resultCode == true else { return nil }
guard let theContainedData = dictionaryData["data"] as? [String:Any] else { return nil }
guard let sampleListData = theContainedData["list"] as? [[String : Any]] else { return nil }
var myListData = [MyEstimateListData]()
for theSample in sampleListData {
guard let existNo = theSample["no"] as? Int, existNo > 0 else {
continue
}
myListData.append( ... )
}
return myListData
how to filter wrong data or invalid data using swift 4.0 Codable ??
you can make codable for inital resonse
Here is your model:
import Foundation
struct Initial: Codable {
let data: DataModel?
}
struct DataModel: Codable {
let result, totalCount: Int
let list: [List]?
enum CodingKeys: String, CodingKey {
case result
case totalCount = "total_count"
case list
}
}
struct List: Codable {
let no, category, templateSeq: Int
enum CodingKeys: String, CodingKey {
case no, category
case templateSeq = "template_seq"
}
}
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And use it like that :
if let initail = try? Initial.init(data: data) , let list = initail.data?.list {
var myListData = list.filter { $0.no > 0 }
}
Yes, you have to use filters for that with codable:
1: Your struct should be according to your response like that:
struct SampleModel : Codable {
var result: Int?
var total_count: Int?
var list: [List]?
}
struct List : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
2: Parse your response using a codable struct like that:
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictionaryData["data"] as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
let resultData = try JSONDecoder().decode(SampleModel.self, from: jsonData)
success(result as AnyObject)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
3: now you can filter invalid data simply:
let filterListData = resultData.list?.filter({$0.no > 0})
let invalidData = resultData.list?.filter({$0.no <= 0})
I have a json response like this
{
"name":"test",
"params":{
"param1":"testA",
"param2":4055,
"param3":9593.34959,
"question":"is this is a test?",
"anything":"testing?",
"random":true
},
"price":103.3
}
My codable struct looks like this
struct:Codable {
var name:String
var params:[String:String]?
var price:Double
}
I have set params to optional because sometimes there are no params but a lot of times there are and codable has an issue because I don't know what types the values in the params dictionary are. I don't even know what the keys are sometimes. I just want to parse them as a dictionary of keys and values with values of type Bool, Int, Double, or String. so a dict like this
let params = ["paramA":1, "param2":"test", "param3":true]
or in the case of the above json this:
let params = ["param1":"testA", "param2":4055, "param3": 9593.34959, "question":"is this is a test?", "anything":"testing?", "random":true]
I am pretty sure I have to create a custom decoder but not exactly sure how to do it.
In your case it's easier to decode json manually like so:
public enum SerializationError: Error {
case wrongRootElement(String)
case missing(String)
case invalid(String, Any)
}
struct YourStruct {
var name:String
var params:[String: String]?
var price:Double
}
extension YourStruct {
public init(json: Any) throws {
guard let jsonDict = json as? [String: Any] else {
throw SerializationError.wrongRootElement("Expected json dictionary not \(json)")
}
guard let name = jsonDict["name"] as? String else {
throw SerializationError.missing("name")
}
let params = jsonDict["params"] as? [String: Any]
let paramsStrs = params?.reduce(into: [String: String]()) { (result, keyValue) in
result[keyValue.key] = String(describing: keyValue.value)
}
guard let price = jsonDict["price"] as? Double else {
throw SerializationError.missing("price")
}
self.init(name: name, params: paramsStrs, price: price)
}
}
let anyYourStruct = try? JSONSerialization.jsonObject(with: jsonData, options: [])
let yourStruct = try? YourStruct(json: anyYourStruct)