I am parsing a response that needs to be transformed from a dictionary on the server (which is a legacy data format) - to simply an array of strings on the client side. Therefore I am wanting to decode the key called 'data' as a dictionary, so i can iterate through the keys and create an array of strings on the client side.
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
let some_data_dictionary = try values.decode([String:Any].self, forKey: CodingKeys.data)
for (kind, values) in some_data_dictionary {
self.data_array.append(kind)
}
} catch {
print("we could not get 'data' as [String:Any] in legacy data \(error.localizedDescription)")
}
}
The error I am getting is: Ambiguous reference to member 'decode(_:forKey:)'
Looks like Swift 'Codable' cant support Any or use of [String:Any], so using this post here Swift 4 decodable nested json with random key attributes
I was able to make a struct for a class I wouldn't use called LegacyData, and then unpack the keys into an array of strings
do
{
let legacy_data = try values.decode([String:LegacyData].self, forKey: CodingKeys.data)
self.array = Array(legacy_data.keys)
}
catch
{
print("no legacy_data \(error) \n")
}
Related
I have seen a piece of code online which uses Swift Codable do decode a JSON into a struct.
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let stringValue = try? container.decode(String.self, forKey: .someId), let value = Int64(stringValue) {
someId = value
} else {
someId = try container.decode(Int64.self, forKey: .someId)
}
}
This code:
decodes a string
tries to parse it into Int64
if it fails - it directly attempts to decode an Int64
My question is - is this code superfluous?
Is there any scenario where Int64.init(_:) from String would be able to decode something that JSONDecoder.decode wouldn't?
And actually, isn't this "decode String - init Int64" the exact same thing that JSONDecoder does under the hood?
It is not superfluous. It could be used to handle JSON that sometimes has numbers encoded as a string (within quotes), and sometimes just as a number.
For example, the JSON could sometimes be:
{
"someId": "12345"
}
in which case you need to decode to String, and then Int64.init
And sometimes the JSON could be:
{
"someId": 12345
}
in which case decoding to String would fail, and you would directly decode to Int64.
What I want to do:
I want to get an array from UserDefaults that I saved beforehand and append a custom object to it. Afterwards I want to encode it as a Data-type again and set this as the UserDefaults Key again.
My problem:
The encoding part is what is not working as intended for me.
It says: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60000011a540
But I do not know how to fix this.
Below is my code for more context:
do {
let decoded = defaults.object(forKey: "ExArray") as! Data
var exo = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoded) as! [Exerc]
exo.append(datas[indexPath.row])
let enco = try NSKeyedArchiver.archivedData(withRootObject: exo, requiringSecureCoding: false) <- Here is the error
defaults.set(enco, forKey: "ExArray")
} catch {
print("Error encoding custom object NOSEARCHO")
}
This is how Exerc looks:
struct Exerc: Codable {
var title: String
var exID: String
}
Seems like you are not using the archiver features, so why don't you just use the codable?
do {
let key = "ExArray"
let decoded = defaults.data(forKey: key)!
var exo = try JSONDecoder().decode([Exerc].self, from: decoded)
exo.append(datas[indexPath.row])
let enco = try JSONEncoder().encode(exo)
defaults.set(enco, forKey: key)
} catch {
print("Error encoding/decoding custom object NOSEARCHO", error)
}
It just a simple refactored MVP of the original code, but you can even work a bit on this and make it human readable right in the plist file!
I'm a beginner in swift and I'm currently making an app that makes a web request. I've been trying to parse this JSON Data but the nested data is just really hard to wrap my head around:
"abilities": [
{
"ability": {
"name": "chlorophyll",
"url": "https://pokeapi.co/api/v2/ability/34/"
},
"is_hidden": true,
"slot": 3
},
{
"ability": {
"name": "overgrow",
"url": "https://pokeapi.co/api/v2/ability/65/"
},
"is_hidden": false,
"slot": 1
}
]
JSon Serialization Code
let jsonAny = try JSONSerialization.jsonObject(with: data, options: [])
guard let json = jsonAny as? [String: Any] else { return }
This is my attempt to manually parse the JSON Data
private func parsePokemonManual(json: [String: Any]) -> Pokemon {
let abilities = json["abilities"] as? [String: Any] ?? [String: Any]()
return Pokemon(abilities: abilities)
}
}
These are the structs that I made to hold the data.
struct Abilities {
let ability : Ability
struct Ability {
let name : String
}
}
How do I successfully parse the JSON Data into an object of Pokemon structure?
With this code so fat I am getting the error "Cannot convert the value of type '[String : Any]' to expected argument type '[Abilities]'. My problem is that I don't know what type to cast the abilities as and that my struct 'Abilities' is also incorrect.
There are 3 problems with your attempt although one might argue there is only 1, that you should use Codable instead but lets stay with JSONSerialization here. The problems are
You are reading the json wrong and should cast not to a dictionary but an array of dictionaries when accessing "abilities"
Your struct is to complicated, maybe because of the previous problem
Lastly, you can't cast into a custom type, you need to convert or map the data into your type by telling exactly what values to use and how because the compiler doesn't understand how to do it.
First the struct can be simplified to
struct Ability {
let name : String
}
And the rest is fixed in the function parsePokemonManual. First get "abilities" and cast to an array of dictionaries. Then map each item in the array by getting "ability" and casting it to a dictionary that is used to get the "name" value that is then used when creating an instance of Ability
private func parsePokemonManual(json: [String: Any]) -> [Ability] {
guard let abilities = json["abilities"] as? [[String: Any]] else {
return []
}
return abilities.compactMap { dict in
guard let ability = dict["ability"] as? [String: String], let name = ability["name"] else { return nil }
return Ability(name: name)
}
}
I'm using MultipeerConnectivity to share SCNNodes position in a multiuser AR session.
When I archive (with NSKeyedArchiver.archivedData(withRootObject: someARNode, requiringSecureCoding: true) )
And unarchive (with if let node = try NSKeyedUnarchiver.unarchivedObject(ofClass:SCNNode.self, from: data) {)
Everything works fine, but now I'm trying to send a custom Object like this:
struct ARUser: Codable {
var idUser : String
var position: [Double]
}
When I try to unarchive the object received with the NSKeyedUnarchiver.unarchivedObject it let me error.
if let node = try NSKeyedUnarchiver.unarchivedObject(ofClass:ARUser.self, from: data) {...}
I get the syntax error: Incorrect argument label in call (have 'ofClass:from:', expected 'ofClasses:from:')
But if I change the function as suggested by the compiler:
if let node = try NSKeyedUnarchiver.unarchivedObject(ofClasses:[ARUser.self], from: data) {..}
I get the next syntax error: Cannot convert value of type 'ARUser.Type' to expected element type 'AnyObject.Type'
So, the question here is, what's the correct way to unarchive custom Objects?
Since here you use Codable
struct ARUser: Codable {
Then
do {
let dataToObject = try JSONDecoder().decode(ARUser.self,from:data)
let objectToData = try JSONEncoder().encode(dataToObject)
}
catch {
print(error)
}
NSKeyedUnarchiver is an old Objective-C stuff
According to the JSON standard RFC 7159, this is valid json:
22
How do I decode this into an Int using swift4's decodable? This does not work
let twentyTwo = try? JSONDecoder().decode(Int.self, from: "22".data(using: .utf8)!)
It works with good ol' JSONSerialization and the .allowFragments
reading option. From the documentation:
allowFragments
Specifies that the parser should allow top-level objects that are not an instance of NSArray or NSDictionary.
Example:
let json = "22".data(using: .utf8)!
if let value = (try? JSONSerialization.jsonObject(with: json, options: .allowFragments)) as? Int {
print(value) // 22
}
However, JSONDecoder has no such option and does not accept top-level
objects which are not arrays or dictionaries. One can see in the
source code that the decode() method calls
JSONSerialization.jsonObject() without any option:
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
// ...
return value
}
In iOS 13.1+ and macOS 10.15.1+ JSONDecoder can handle primitive types on root level.
See the latest comments (Oct 2019) in the linked article underneath Martin's answer.