I have this JSON save in file ("list_catalog.json"):
[
{
"idcatalog": 1,
"imgbase64": "",
"urlImg": "http://172.../page01.jpg",
"urlPages": "http://172.../catalog_1_pages.json",
"dt_from": "",
"dt_to": ""
},
{
"idcatalog": 2,
"imgbase64": "",
"urlImg": "http://172.../1.jpg",
"urlPages": "http://172.../2_pages.json",
"category": [
{
"id": 1,
"lib": "lib"
}
],
"dt_to": ""
}
]
I can get this file with :
if let url = URL(string: "http://172.../list_catalogs.json") {
do {
let contents = try String(contentsOf: url)
print(contents)
} catch {
// contents could not be loaded
}
}
But I can't convert this String in dictionary.
For convert data to string I use this function :
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
But for this situation it doesn't work
Somebody can help me ?
The problem with the code you have is that you are trying to convert an array to a dictionary (as rightly pointed out by a couple of people in the comments Vadian, Moritz at the time of writing).
So the obvious, first step solution would be instead of casting to [String: Any] cast to [[String: Any]] (an array of dictionaries) but this then still leaves you with the problem of the Any part. To work with this dictionary you need to know/remember the keys and their types and cast each value as you use it.
It is much better to use the Codable protocol which along with the decoder allows you to basically map the JSON to related code structures.
Here is an example of how you could parse this JSON using the Swift 4 codable protocol (done in a Swift Playground)
let jsonData = """
[{
"idcatalog": 1,
"imgbase64": "",
"urlImg": "http://172.../page01.jpg",
"urlPages": "http://172.../catalog_1_pages.json",
"dt_from": "",
"dt_to": ""
}, {
"idcatalog": 2,
"imgbase64": "",
"urlImg": "http://172.../1.jpg",
"urlPages": "http://172.../2_pages.json",
"category": [{
"id": 1,
"lib": "lib"
}],
"dt_to": ""
}]
""".data(using: .utf8)!
struct CatalogItem: Codable {
let idCatalog: Int
let imgBase64: String?
let urlImg: URL
let urlPages: URL
let dtFrom: String?
let dtTo: String?
let category: [Category]?
enum CodingKeys: String, CodingKey {
case idCatalog = "idcatalog"
case imgBase64 = "imgbase64"
case urlImg, urlPages, category
case dtFrom = "dt_from"
case dtTo = "dt_to"
}
}
struct Category: Codable {
let id: Int
let lib: String?
enum CodingKeys: String, CodingKey {
case id, lib
}
}
do {
let decoder = JSONDecoder()
let result = try decoder.decode([CatalogItem].self, from: jsonData)
print(result)
} catch {
print(error)
}
Console output:
[__lldb_expr_118.CatalogItem(idCatalog: 1, imgBase64: Optional(""), urlImg: http://172.../page01.jpg, urlPages: http://172.../catalog_1_pages.json, dtFrom: Optional(""), dtTo: Optional(""), category: nil), __lldb_expr_118.CatalogItem(idCatalog: 2, imgBase64: Optional(""), urlImg: http://172.../1.jpg, urlPages: http://172.../2_pages.json, dtFrom: nil, dtTo: Optional(""), category: Optional([__lldb_expr_118.Category(id: 1, lib: Optional("lib"))]))]
Now comes the good part, it's much easier to use this data now...
print(result.first?.urlImg)
Output:
Optional(http://172.../page01.jpg)
This works for the example JSON you provided but may need more tweaks based on the rest of your data set.
Related
I'm trying to encode data but struggling to deal with empty array with no type as the API's I am working with needs me to send [] if there is no entry.
[
{
"RequestId": "5B6E36D9-8759-41BB-A0C0-EDFB116DFBB7",
"DataSources": ["5B6E36D9-8759-41BB-A0C0-EDFB116DFBB7"],
"GroupBy": [],
"Filters": []
}
]
above is the object json which I have to send.
struct ResponseElement: Encodable {
let requestID: String
let dataSources: [String]
let groupBy: []
let filters: []
enum CodingKeys: String, CodingKey {
case requestID = "RequestId"
case dataSources = "DataSources"
case groupBy = "GroupBy"
case filters = "Filters"
}
}
let data = ResponseElement(requestID: "5B6E36D9-8759-41BB-A0C0-EDFB116DFBB7",
dataSources: ["5B6E36D9-8759-41BB-A0C0-EDFB116DFBB7", ["5B6E36D9-8759-41BB-A0C0-EDFB116DFBB7]"],
groupBy: [],
filters: [])
let jsonEncoder = JSONEncoder()
let data = try! jsonEncoder.encode(data)
please note while creating data variable I have to pass groupBy, filters as empty array [], I have tried with [nil] which goes as [null] after encoding but it doesn't work in my case, it has to be []
how do I solve this please?
If you know that the array will always be empty, the type of that is [Never]. A Never is a type that cannot be instantiated, so this array is necessarily empty.
Unfortunately, Never does not conform to Encodable. It should. It should conform to every protocol automatically (that would make Never a "bottom type," which is a good feature in a type system). That said, it's straightforward to provide the conformance:
extension Never: Encodable {
public func encode(to encoder: Encoder) throws {
// This line of code can never, ever run. It's impossible to
// create a Never, so it's impossible to run its instance methods.
fatalError()
}
}
struct ResponseElement: Encodable {
let requestID: String
let dataSources: [String]
let groupBy: [Never]
let filters: [Never]
...
}
You may rightly feel uncomfortable extending a stdlib type to conform with a stdlib protocol. This is kind of fragile, since stdlib might create the conformance in the future, or some other package might do it, and there'd be a conflict. So you can do this explicitly by creating a type of your own that encodes an empty unkeyed container:
struct EmptyArray: Encodable {
func encode(to encoder: Encoder) throws {
encoder.unkeyedContainer()
}
}
struct ResponseElement: Encodable {
let requestID: String
let dataSources: [String]
let groupBy: EmptyArray
let filters: EmptyArray
...
}
And finally, you can perform the encoding by hand and get rid of the unnecessary properties (this is how I'd do it myself):
struct ResponseElement: Encodable {
let requestID: String
let dataSources: [String]
enum CodingKeys: String, CodingKey {
case requestID = "RequestId"
case dataSources = "DataSources"
case groupBy = "GroupBy"
case filters = "Filters"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(requestID, forKey: .requestID)
try container.encode(dataSources, forKey: .dataSources)
container.nestedUnkeyedContainer(forKey: .groupBy)
container.nestedUnkeyedContainer(forKey: .filters )
}
}
If this is just for encoding data and you never have to provide anything for this properties you could use any type you want for your property:
e.g.
struct ResponseElement: Encodable {
let requestID: String
let dataSources: [String]
let groupBy: [String] = []
let filters: [String] = []
enum CodingKeys: String, CodingKey {
case requestID = "RequestId"
case dataSources = "DataSources"
case groupBy = "GroupBy"
case filters = "Filters"
}
}
Result:
{
"RequestId": "1",
"GroupBy": [],
"Filters": [],
"DataSources": [
"1",
"2"
]
}
I have some data from server, and I'm using Alamofire SwiftyJSON to convert it to [String: Any]. Then I'm saving it to plist using SwiftyPlistManager. The point is that SwiftyPlistManager crashed when saving <null>, so I need to replace all <null>or nilto "".
My Dictionary after Alamofire SwiftyJSON looks this way:
["info_editable": true,
"name": Android Q,
"is_message": true,
"images": [["id": 92,
"image": /media/product/102.png]],
"video_description": <null>,
"is_valid": true]
or it could be -
["info_editable": true,
"name": Android Q,
"is_message": true,
"images": <null>,
"video_description": <null>,
"is_valid": true]
I suppose to use Codable from raw data, but have no idea how to set initial value as empty string or [[]], then check if parced data is <null> and leave initial value as default.
Or is there any way to list nested dictionary to replace <null>to ""?
You can try
var dic = ["1":nil,"2":"33","3":"5444"]
let res = dic.mapValues { $0 == nil ? "" : $0 }
print(res) // ["1": Optional(""), "2": Optional("33"), "3": Optional("5444")]
for now, my best idea is -
1) stringify every value except array of dictionaries,
2) check if string content "null", then replace it with ""if true.
3) array of dictionaries, if stringified, will have 2 option - with [[ and ]](then checking every dictionary like above) or without - in case of "images": <null>,(so <null> should be replaced with[[]].
but I have about 7 requests with different data, should be parsed this strange way, and I hope to found more pretty decision.
Here's a protocolish solution, that goes recursively through dictionaries and arrays:
/// Allows clients to ask the receiver to remove any `NSNull` instances
protocol NullStripable {
func strippingNulls() -> Self
}
extension Array: NullStripable {
func strippingNulls() -> Self {
return compactMap {
switch $0 {
case let strippable as NullStripable:
// the forced cast here is necessary as the compiler sees
// `strippable` as NullStripable, as we casted it from `Element`
return (strippable.strippingNulls() as! Element)
case is NSNull:
return nil
default:
return $0
}
}
}
}
extension Dictionary: NullStripable {
func strippingNulls() -> Self {
return compactMapValues {
switch $0 {
case let strippable as NullStripable:
// the forced cast here is necessary as the compiler sees
// `strippable` as NullStripable, as we casted it from `Value`
return (strippable.strippingNulls() as! Value)
case is NSNull:
return nil
default:
return $0
}
}
}
}
Usage example:
let dict: [String: Any] = [
"items": ["info_editable": true,
"name": "Android Q",
"is_message": true,
"images": [["id": 92,
"image": "/media/product/102.png"]],
"video_description": NSNull(),
"is_valid": true],
"somethingElse": NSNull()
]
print(dict.strippingNulls())
Sample output:
["items": ["name": "Android Q", "info_editable": true, "images": [["image": "/media/product/102.png", "id": 92]], "is_message": true, "is_valid": true]]
Try this one. It will remove null and replace with blank string, without loosing key.
func removeNullFromResponse(response:NSDictionary) -> NSDictionary{
let blankString = "\"\""
let myMutableDict: NSMutableDictionary = NSMutableDictionary(dictionary: response)
var data = try? JSONSerialization.data(withJSONObject:myMutableDict, options: JSONSerialization.WritingOptions.prettyPrinted)
var strData = NSString.init(data: data!, encoding:String.Encoding.utf8.rawValue)
strData = strData?.replacingOccurrences(of: "<NULL>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "<null>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "<Null>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "NULL", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "null", with: blankString) as NSString?
data = strData?.data(using: String.Encoding.utf8.rawValue)!
var dictionary = NSDictionary()
do
{
dictionary = try JSONSerialization.jsonObject(with: data! , options: JSONSerialization.ReadingOptions()) as! NSDictionary
} catch {
print(error)
}
return dictionary as NSDictionary
}
This question already has answers here:
type any? has no subscript members
(3 answers)
Closed 3 years ago.
From this json i want to retrieve the values of the key "name" and "image"
["count": 12, "results": [{
"pk": 6,
"fields": {
"name": "Kids Wear",
"image": "[imag.jpg]",
}
I have got error as **[ "Type 'Any' has no subscript members"] when i tried to get value using below code
let val = (json["results"]!)
val[0]["fields"] as [string:Any]
You can try using Swift Codable class to get relevant data
private func getResponse() {
let json = "{count: 6,results: [{pk: 6,fields: {name: Ethnic Wear,image:[imag.jpg]}}]}"
let data = json.data(using: .utf8)
let jsonDecoder = JSONDecoder()
do {
let response = try jsonDecoder.decode(Response.self, from: data!)
guard let results = response.results else {
return
}
let pk = results[0].pk ?? 0
let fields = results[0].fields
} catch _ {
}
}
print(getResponse())
// Response.swift
import Foundation
struct Response : Codable {
let count : Int?
let results : [Results]?
}
I have a struct that has a method to return a dictionary representation. The member variables were a combination of different types (String and Double?)
With the following code example, there would be a warning from Xcode (Expression implicitly coerced from 'Double?' to Any)
struct Record {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
func toDictionary() -> [String: Any] {
return [
"name": name,
"frequency": frequency
]
}
}
However if it was returning a type [String: Any?], the warning goes away:
struct Record {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
func toDictionary() -> [String: Any?] {
return [
"name": name,
"frequency": frequency
]
}
}
My question is: Is this correct? And if it is, can you point me to some Swift documentation that explains this?
If it isn't, what should it be?
== EDIT ==
The following works too:
struct Record {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
func toDictionary() -> [String: Any] {
return [
"name": name,
"frequency": frequency as Any
]
}
}
You can cast frequency to Any since the latter can hold any type. It is like casting instances of specific Swift type to the Objective-C id type. Eventually, you'll have to downcast objects of the type Any to a specific class to be able to call methods and access properties.
I would not recommend structuring data in your code using Any, or if you want to be specific Any? (when the object may or may not hold some value). That would be a sign of bad data-modeling.
From the documentation:
Any can represent an instance of any type at all, including function types.[...] Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work within your code.
(emphasis is mine)
Instead, use the Data type. And you would be able to decode Record or encode it from and into Data:
struct Record : Codable {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
init(data: Data) throws {
self = try JSONDecoder().decode(Record.self, from: data)
}
func toData() -> Data {
guard let data = try? JSONEncoder().encode(self) else {
fatalError("Could not encode Record into Data")
}
return data
}
}
And use it like so:
let record = Record(name: "Hello", frequency: 13.0)
let data = record.toData()
let decodedRecord = try Record(data: data)
print(decodedRecord.name)
print(decodedRecord.frequency ?? "No frequency")
I'd recommend adding Codable conformance, and letting JSONEncoder do all the heavy lifting. If however you are constrained to the toDictionary approach, then I would advise against [String:Any?], since that might result in undefined behaviour (try to print the dictionary for more details).
A possible solution for toDictionary is to use an array of tuples that gets converted to a dictionary:
func toDictionary() -> [String: Any] {
let propsMap: [(String, Any?)] = [
("name", name),
("frequency", frequency)
]
return propsMap.reduce(into: [String:Any]()) { $0[$1.0] = $1.1 }
}
This way the nil properties simply don't receive entries in the output dictionary.
let json: [AnyObject] = {
"response": "get_nearby_deals",
"userID": "12345",
"demo":[{"deal_code":"iD1612061"}]
}
How to declare Dictionary in Swift? I'm new in Swift. Totally stuck.
You have declared Array using [AnyObject], just change it to [String: Any] and replace curly braces {} with square brackets [].
let json: [String: Any] = [
"response": "get_nearby_deals",
"userID": "12345",
"demo":[["deal_code":"iD1612061"]]
]
And you can retrieve value from Dictionary using subscript like this.
let userID = json["userID"] as! String
//Above will crash if `userID` key is not exist or value is not string, so you can use optional wrapping with it too.
if let userID = json["userID"] as? String {
print(userID)
}
//`demo` value is an array of [String:String] dictionary(s)
let demo = json["demo"] as! [[String:String]]
//Same here as the previous `userID`, it will crash if 'demo' key is not exist, so batter if you optionally wrapped it:
if let demo = json["demo"] as? [[String:String]] {
print(demo)
}