I am using the following struct that conforms to Codable to represent my data within a Set, I then take this set encode it into a JSON Array that looks like the following [{"myexample":"Example5","id":3}]
Struct and Encoding:
struct Model : Codable, Hashable {
var myexample: String?
var id: Int?
}
var mySet: Set<Model> = []
mySet.insert(Model(myexample: "Example4", id: 3))
do {
let json = try JSONEncoder().encode(mySet)
print(String(data: json, encoding: .utf8)!)
} catch {
print(error)
}
How can I inversely decode this array using the following function?
Currently getting this error:
Value of type 'Set' has no member 'utf8'
func processArray(array:Set<Model> = []) {
do{
let mydata = Data(array.utf8.data)
let decoded = try JSONDecoder().decode([Model].self,from: mydata)
print(decoded)
}catch let jsonErr {
print(jsonErr)
}
}
JSON is a string format so a JSON array is also string
func processJSON(_ json: String) {
do{
let mydata = Data(json.utf8)
let decoded = try JSONDecoder().decode(Set<Model>.self,from: mydata)
print(decoded)
} catch {
print(error)
}
}
Related
Here I want to be able to use the value returned from an array. It returns as a type from a struct. I'm unsure of how to use the value as an integer.
struct Item: Codable {
let data: [String : Datum]
}
struct Datum: Codable {
let value: Int
}
var array = Item(data: ["1" : Datum(value: 1),"2": Datum(value: 2), "3":Datum(value: 3)])
var keyArray = ["1", "2", "3"]
print(array.data[keyArray[0]]!)
// Prints Datum(value: 1)
print(array.data[keyArray[0]]! + 1)
//This produces an error "Cannot convert value of type 'Datum' to expected argument type 'Int'"
//Expected result should be 2
My use case is when I get returned a decoded JSON it normally comes back as a dictionary. I'm wanting to use the values returned with a key but I feel like I'm one step short.
Context
Full JSON Link
I'm going to retrieve values from this JSON. (Example from large JSON file)
{"data":{"2":{"high":179,"highTime":1628182107,"low":177,"lowTime":1628182102},"6":{"high":189987,"highTime":1628179815,"low":184107,"lowTime":1628182100},"8":{"high":190800,"highTime":1628181435,"low":188100,"lowTime":1628182095}
}}
The string in front refers to an item ID.
The struct that I came up to decode goes like this.
// MARK: - Single
struct Single: Codable {
let data: [String: Datum]
}
// MARK: - Datum
struct Datum: Codable {
let high, highTime: Int
let low, lowTime: Int?
}
From there I'm planning to iterate through the JSON response to retrieve the item prices I'd want.
#available(iOS 15.0, *)
struct ContentView: View {
#State var dataFromURL: Single = Single(data: [:])
var body: some View {
VStack {
Text("Hello, world!")
.padding()
}
.onAppear {
async {
try await decode()
}
}
}
func decode() async throws -> Single {
let decoder = JSONDecoder()
let urlString = "https://prices.runescape.wiki/api/v1/osrs/latest"
guard let url = URL(string: urlString) else { throw APIError.invalidURL }
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { throw APIError.invalidServerResponse }
guard let result = try? decoder.decode(Single.self, from: data) else { throw APIError.invalidData }
//We copy our result to an existing variable
dataFromURL = result
return result
}
}
enum APIError: Error {
case invalidURL
case invalidServerResponse
case invalidData
}
extension APIError: CustomStringConvertible {
public var description: String {
switch self {
case.invalidURL:
return "Bad URL"
case .invalidServerResponse:
return "The server did not return 200"
case .invalidData:
return "Their server returned bad data"
}
}
}
I haven't gotten further than grabbing the response from the URL. That is why once I start manipulating the data I'd like to use the response to find other things like what would a profit/loss with another item become. Which isn't the goal of this question at the moment.
The object model to parse that JSON would be:
struct Price: Decodable {
let high: Int?
let highTime: Date?
let low: Int?
let lowTime: Date?
}
struct ResponseObject: Decodable {
let prices: [String: Price]
enum CodingKeys: String, CodingKey {
case prices = "data"
}
}
(Note, the documentation says that either high or low might be missing, so we have to make them all optionals.)
Now, the id number is being passed as a string in the JSON/ResponseObject. But that is a number (look at mapping). So, I would remap that dictionary so that the key was an integer, e.g.
enum ApiError: Error {
case unknownError(Data?, URLResponse?)
}
func fetchLatestPrices(completion: #escaping (Result<[Int: Price], Error>) -> Void) {
let url = URL(string: "https://prices.runescape.wiki/api/v1/osrs/latest")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
completion(.failure(error ?? ApiError.unknownError(data, response)))
return
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
do {
let responseObject = try decoder.decode(ResponseObject.self, from: responseData)
let keysAndValues = responseObject.prices.map { (Int($0.key)!, $0.value) }
let prices = Dictionary(uniqueKeysWithValues: keysAndValues)
completion(.success(prices))
} catch {
completion(.failure(error))
}
}
task.resume()
}
The code that converts that [String: Price] to a [Int: Price] is this:
let keysAndValues = responseObject.prices.map { (Int($0.key)!, $0.value) }
let prices = Dictionary(uniqueKeysWithValues: keysAndValues)
I must say that this is a questionable API design, to have keys returned as integers in one endpoint and as strings as another. But it is what it is. So, the above is how you handle that.
Anyway, now that you have a dictionary of prices, keyed by the id numbers, you can use that in your code, e.g.
var prices: [Int: Price] = [:]
var products: [Product] = []
let group = DispatchGroup()
group.enter()
fetchLatestPrices { result in
defer { group.leave() }
switch result {
case .failure(let error):
print(error)
case .success(let values):
prices = values
}
}
group.enter()
fetchProducts { result in
defer { group.leave() }
switch result {
case .failure(let error):
print(error)
case .success(let values):
products = values }
}
group.notify(queue: .main) {
for product in products {
print(product.name, prices[product.id] ?? "no price found")
}
}
Where
func fetchProducts(completion: #escaping (Result<[Product], Error>) -> Void) {
let url = URL(string: "https://prices.runescape.wiki/api/v1/osrs/mapping")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
completion(.failure(error ?? ApiError.unknownError(data, response)))
return
}
do {
let products = try JSONDecoder().decode([Product].self, from: responseData)
completion(.success(products))
} catch {
completion(.failure(error))
}
}
task.resume()
}
And
struct Product: Decodable {
let id: Int
let name: String
let examine: String
let members: Bool
let lowalch: Int?
let limit: Int?
let value: Int
let highalch: Int?
let icon: String
}
(As an aside, I do not know if some of these other properties should be optionals or not. I just used optionals where I empirically discovered that they are occasionally missing.)
I have a json like bellow ..
{"type": "Polygon","coordinates":[[[90.40082675305842,23.708825220302813],[90.4018551231959,23.708188760430843],[90.40247361862504,23.7091957460091],[90.40143983815886,23.70975584674032],[90.40082675305842,23.708825220302813]]]}
I have used bellow code to parse which returns nil
let json = myjsonString as? [String: Any]
But it returns nil. please help me to parse above json
You have to deserialize the string, either with JSONSerialization or – more comfortable – with JSONDecoder
let jsonString = """
{"type": "Polygon","coordinates":[[[90.40082675305842,23.708825220302813],[90.4018551231959,23.708188760430843],[90.40247361862504,23.7091957460091],[90.40143983815886,23.70975584674032],[90.40082675305842,23.708825220302813]]]}
"""
struct Overlay : Decodable {
let type : String
let coordinates : [[[Double]]]
}
do {
let result = try JSONDecoder().decode(Overlay.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
At first, you need to convert data from your input string then you can convert data to dictionary object. You can try this example.
let jsonText = "{\"first_name\":\"Sergey\"}"
var dictonary:NSDictionary?
if let data = jsonText.data(using: String.Encoding.utf8) {
do {
dictonary = try JSONSerialization.jsonObject(with: data, options: []) as? [String:AnyObject]
if let myDictionary = dictonary {
print(" First name is: \(myDictionary["first_name"]!)")
}
} catch let error as NSError {
print(error)
}
}
I'm trying to create a generic decode function to decode my two different models. I get the error "Argument type 'PrivateSchoolType.Type' does not conform to expected type 'Decodable'".
Model
struct PrivateSchoolModel: Decodable {
var name: String
var id: String
var city: String
var state: String
}
Calling Function
function getInfo() {
// does not work => ERROR
guard let schools = decode(jsonData: jsonData, using: PrivateSchoolModel) else { return }
// does work
guard let schools = specificDecode()
}
Specific Decode Function (DOES WORK)
private func specificDecode() -> [PrivateSchoolModel]? {
guard let jsonData = getJSONData(from: .privateSchool) else { return }
do {
let decoder = JSONDecoder()
let schools = try decoder.decode([PrivateSchoolModel].self, from:
jsonData)
return schools
} catch let error {
print("Error: \(error.localizedDescription)")
}
return nil
}
Generic Decode Function (DOES NOT WORK)
private func decode<M: Decodable>(jsonData: Data, using model: M) -> [M]? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let schools = try decoder.decode([M].self, from:
jsonData) //Decode JSON Response Data
return schools
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
Change the method signature as below,
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let schools = try decoder.decode(modelType, from: jsonData) //Decode JSON Response Data
return schools
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
Usage
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return }
I am trying to extend Dictionary with the following code:
extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject {
var jsonString: String? {
if let dict = (self as AnyObject) as? Dictionary<String, AnyObject> {
do {
let data = try JSONSerialization.data(withJSONObject: dict, options: JSONSerialization.WritingOptions(rawValue: UInt.allZeros))
if let string = String(data: data, encoding: String.Encoding.utf8) {
return string
}
} catch {
print(error)
}
}
return nil
}
}
Where I write something like:
let x: [String: String] = ["": ""]
x.jsonString
I get this error:
Value of type '[String: String]' as no member 'jsonString'
Anything I am missing?
There is no need to constrain Dictionary Value type at all:
extension Dictionary where Key: ExpressibleByStringLiteral {
var jsonString: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self)
else { return nil }
return String(data: data, encoding: .utf8)
}
}
Since String is a value type , look for it's
public struct String {
and AnyObject refers to any instance of a class only , and is equivalent to id in Objective-C , so this declaration of x
[String: String] doesn't fit with [String: AnyObject]
because Any refers to any instance of a class, struct, or enum so it'll fit perfectly
let encoder = JSONEncoder()
do {
let encodData = try encoder.encode("test string") // same as Int type
print(encodData) // nil
} catch let err {
print(err.localizedDescription) // The data couldn’t be written because it isn’t in the correct format.
}
how to encode these type value
The top-level (root) JSON object can only be an array or dictionary. For example:
do {
let encoder = JSONEncoder()
let encodData = try encoder.encode(["test string"])
print(String(data: encodData, encoding: .utf8)!)
// ["test string"]
} catch {
print(error.localizedDescription)
}