Swift JSON parse with colon in JSON key name - json

Can you use Swift's JSONDecoder on JSON that has a colon in the key name? Colons are reserved in Swift for things like indicating Type and Protocol, so when I try to create a struct with a matching keyname to use with JSONDecoder.decode, I'll get an error. Example API:
https://api.teleport.org/api/urban_areas/
the list of cities I'd like to access is in the key:
"ua:item"
But Swift doesn't allow a property with this name for easy JSONDecoding.
I'd like to stick with JSONDecoder since it's so easy & elegant. Is there an easy workaround for this, or do I need to fall back on older parsing techniques. Thanks!

Here is a reduced version of the JSON response for the API that you are calling. I have just limited the number of items in each array as it becomes to repetitive to just list similar items over and over again.
let data = """
{
"_links": {
"curies": [
{
"href": "https://developers.teleport.org/api/resources/Location/#!/relations/{rel}/",
"name": "location",
"templated": true
}
],
"self": {
"href": "https://api.teleport.org/api/urban_areas/"
},
"ua:item": [
{
"href": "https://api.teleport.org/api/urban_areas/slug:aarhus/",
"name": "Aarhus"
},
{
"href": "https://api.teleport.org/api/urban_areas/slug:adelaide/",
"name": "Adelaide"
}
]
},
"count": 266
}
""".data(using: .utf8)!
There are are couple of gotchas in the JSON, but we can fix them easily by using CodingKeys. Notice the case value of the CodingKey must match the variable name, the string value of the coding key must match the value in the JSON, where the values are the same we can skip writing the string value.
There are three issues with the JSON
_links
self
ua:item
By convention variables in Swift do not usually start with an underscore, so it makes sense to remove that. We can do that with the first set of CodingKeys.
self is a reserved word in Swift so we should replace that with something more appropriate, in this case I chose link.
As you have already noticed you cannot have colons in the name of variables. We can replace this with something more suitable, in this case uaItem.
This gives the following struct. Which if you take with the above data variable and past into a playground it should all decode nicely.
struct Response: Decodable {
let links: Links
let count: Int
// These are the coding keys for Response
enum CodingKeys: String, CodingKey {
case links = "_links"
case count
}
struct Links: Decodable {
let curies: [Curie]
let link: HREF
let uaItem: [UAItem]
// These are the coding keys for Links
enum CodingKeys: String, CodingKey {
case curies
case link = "self"
case uaItem = "ua:item"
}
}
struct Curie: Decodable {
let href: String
let name: String
let templated: Bool
}
struct HREF: Decodable {
let href: String
}
struct UAItem: Decodable {
let href: String
let name: String
}
}
do {
let result = try JSONDecoder().decode(Response.self, from: data)
print(result)
} catch {
print(error)
}

Related

Decoding JSON that has a dynamic key value with the key value name found elsewhere in JSON model using Swift Decodable

When I make an API request, I get returned a JSON structure that contains several objects which all have unique key value names. Therefore I cannot use the regular Decodable protocol to break down the JSON into different instances. However, these key value names are found elsewhere in my JSON structure under known constant values that I can access normally. Here is some example code I created to demonstrate my issue:
let jsonString = """
{
"uniqueObjects": {
"uniqueObject:1132435": {
"firstName": "John"
"lastName": "Smith"
}
"uniqueObject2:119672": {
"firstName": "Jane"
"lastName": "Doe"
}
"uniqueObject3:008997": {
"firstName": "Sam"
"lastName": "Greenfield"
}
}
"keys": {
"object1": {
"key": "uniqueObject1:132435"
}
"object2": {
"key": "uniqueObject2:119672"
}
"object3": {
"key": "uniqueObject3:008997"
}
}
"""
let jsonData = Data(jsonString.utf8)
let decodedData = try? JSONDecoder().decode(JSONData.self, from: jsonData)
print(decodedData?.uniqueObjects.firstObject.firstName ?? "No data decoded")
struct JSONData: Decodable {
let uniqueObjects: Object
let keys: KeyObjects
}
struct Object: Decodable {
let firstObject: Names
let secondObject: Names
let thirdObject: Names
private enum DynamicCodingKeys: String, CodingKey {
case firstObject = "???" // this needs to be mapped to the unique value for object 1
case secondObject = "??" // this needs to be mapped to the unique value for object 2
case thirdObject = "????" // etc.
// I don't think this works because Xcode says that the strings must be raw literals
}
}
struct KeyObjects: Decodable {
let object1: Key
let object2: Key
let object3: Key
}
struct Key: Decodable {
let key: String
}
struct Names: Decodable {
let firstName: String
let lastName: String
}
The approach I took here, which is definitely wrong, was to create a coding key for each of the unique objects and map its name to a String that would be somehow be decoded from its relative key value pair in the key object. CodingKeys, at least what I have tried, do not allow you to do this so I need a new method in order to access this code. I also need to know how I can reference the data once I decode it (just printing it out for now). Help and a short explanation would be much appreciated as I am a beginner developer. Thanks!
Unless I have misunderstood it looks to me like you are over complicating things. This is how I would define the types for decoding the json
struct Response: Codable {
let uniqueObjects: [String: User]
let keys: [String: ObjectKey]
}
struct ObjectKey: Codable {
let key: String
}
struct User: Codable {
let firstName: String
let lastName: String
}

Swift JSON string to dictionary not able to parse all values

I download a JSON file from my database which returns the following string:
["ingredients": asdasdasd,
"price": 14,
"_id":
{
"$oid" = 5e8e3706f00ca80f251485c3;
},
"category": sadad,
"available": Disponibile,
"name": asdadasd]
I then convert this string to data to then convert it to a Dictionary<String, Any>
if let myData = responseString.data(using: .utf8) {
do {
let myArray = try (JSONSerialization.jsonObject(with: myData) as? [Dictionary<String, Any>])!
completion(myArray, nil)
} catch let error as NSError {
print("error:" + String(describing: error))
completion(nil, error)
}
}
This works perfectly fine, as I can get, let's say, the price parameter doing myArray["price"].
The problem arises when I try to get the Id parameter, as when I do myArray["_id"] I get:
{
"$oid" = 5e8e370af00ca80f251485cf;
}
I would like to directly get the ID parameter, and I can't parse this value to JSON as it is not in JSON format. At the moment I am fixing the issue by manipulating this string replacing the = with :, removing the ; and other nasty stuff, but I am sure there is a more efficient way to solve the issue.
myArray["_id"] is a Dictionary in your myArray.So you have to convert your myArray["_id"] to dictionary and then you can access the id.
try this
let id = (myArray["_id"] as Dictionary ?? [:])["$oid"] as? String ?? ""
What you've posted looks like debugger print output, not JSON from your server. I'm going to assume that your JSON actually looks like this:
[
{
"ingredients": "asdasdasd",
"price": 14,
"_id": {
"$oid": "5e8e3706f00ca80f251485c3"
},
"category": "sadad",
"available": "Disponibile",
"name": "asdadasd"
}
]
Given that, you could use a model struct like
struct Recipe: Codable {
let ingredients: String
let price: Int
let id: ID
let category, available, name: String
enum CodingKeys: String, CodingKey {
case ingredients, price
case id = "_id"
case category, available, name
}
}
struct ID: Codable {
let oid: String
enum CodingKeys: String, CodingKey {
case oid = "$oid"
}
}
typealias Recipes = [Recipe]
to parse it using
do {
let recipes = try JSONDecoder(Recipes.self, from: myData)
let firstOid = recipe.first?.id.oid
} catch {
print(error)
}
That said, I would recommend avoiding generic names like myArray for your variables.
Also, when retrieving JSON data from your server, it's not necessary to first convert them to a String and then back to Data before passing it to the JSON parser - simply pass the raw server data along.

Swift decode json with key starting as a number

I have a json in following format:
let json = """
{
"stuff": {
"1": "one",
"2": "two",
"4": "four"
}
}
question is how can i make my
struct Stuff: Codable, Equatable {
let 1: String
let 2: String
let 4: String
}
compile and work?
i use to call this with below, and it works fine for everything but if let name starts with number it obviously won't compile
let obj = try? JSONDecoder().decode(T.self, from: data)
You can't. A variable must not start with a numeric character. Unalterable rule.
But you can map the names with CodingKeys
struct Stuff: Codable, Equatable {
let one, two, four: String
private enum CodingKeys : String, CodingKey { case one = "1", two = "2", four = "4"}
}
You can't. RFC 7159 standard for JSON dictates that the object key must be a string.
object = begin-object [ member *( value-separator member ) ]
end-object
member = string name-separator value

decoding JSON array or dictionary error swift

This is my first time taking a shot at Codable/Decodable and i would like to decode a JSON. I am attempting to access the "name" and "description" keys within the events array. Below is a snippet of the JSON - im getting this error within my code
"Expected to decode Dictionary but found an array instead."
"pagination": {
"page_number": 1,
"page_size": 50,
"continuation": "eyJwYWdlIjogMn0",
"has_more_items": true
},
"events": [
{
"name": {
"text": "Genesis Part 4",
"html": "Genesis Part 4"
},
"description": {
"text": "Wednesday, June 6-June 27, 2018\n12:00-2:15 PM, Eastern Time\n\u00a0\nCOED\n\u00a0\nBible Study Leader:\u00a0Nicki Cornett\n\u00a0\nContact:NickiCornett#gmail.com\n\u00a0\nGenesis Part 4 -\u00a0Wrestling with God - A Study on Isaac, Jacob, and Esau- Precept Workbook (NASB)\n\u00a0\nGod renews His covenant promise with Abraham through Isaac and Jacob.
Here is how i went about decoding - (NOTE - "description" is not here yet because i having an issue working into the events array to access name & descritption
struct Eventbrite: Decodable {
private enum CodingKeys : String, CodingKey { case events = "events", name = "name"}
let events: [String:[String]]
let name: [String:[String]]
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let eventbriteData = try JSONDecoder().decode(Eventbrite.self, from: data)
print(eventbriteData.name)
name is clearly not in the scope of pagination and events (note the {}) and is a regular [String:String] dictionary which can be decoded into another struct.
Decode this (as description is incomplete I left it out), you don't need CodingKeys:
struct Eventbrite: Decodable {
let events: [Event]
}
struct Event: Decodable {
let name: Name
// let description: [String:String]
}
struct Name : Decodable {
let text, html : String
}
Right so Decodable is actually pretty smart in that you really don't need to write any code to do the decoding yourself. You just have to make sure you match the JSON structure (and make structs that also conform to Decodable for any nested objects). In other words, instead of having variables as dictionaries, make them their own Decodable struct.
So for example:
struct EventBrite: Decodable {
let pagination: Pagination
let events: [Event]
}
struct Pagination: Decodable {
let page_number: Int
let page_size: Int
let continuation: String
let has_more_items: Bool
}
struct Event: Decodable {
let name: EventName
let description: EventDescription
}
struct EventName: Decodable {
let name: String
let html: String
}
etc...
Something else that's important here is if a key or property is not guaranteed to be returned (like let's say that the EventName doesn't always have an html value that comes back from the server you can easily just mark that value as optional. So something like:
struct EventName: Decodable {
let name: String
let html: String?
}
Another side note, you actually messed up your dictionary type declarations. You'll notice that event is actually of type [String: [String: String]] since the key is a string and the values seem to always be dictionary. And name is [String: String]. Which is not what you had them down as in your original question.
When the values can be different like with pagination you'll want to do something like [String: Any] so just be careful about that.
HOWEVER The approach I suggested I think is better than having properties be dictionaries. For one you don't have to worry about declaring the type of dictionary it is (which you made some small errors on). But more importantly when each dictionary just becomes its own clearly defined struct and you don't have to worry about remembering or looking up the keys. Dot syntax/auto complete will automatically tell you what there can be! (And no casting when your value is of type Any or AnyObject!)
Also definitely use structs for all these as I once benchmarked performance and measured structs efficiency on the order of magnitude of millions of times more efficient than classes. Just a FYI.

parsing JSON with a decodable?

I have a JSON file:
{
"name": "Jens",
"time": "11.45",
"date": "2018:04:17",
"differentTimestamps":[""]
"aWholeLotOfnames":{
"name1": "Karl"
"name2": "pär"
}
How to parse above JSON ? I have checked this tutorial https://www.youtube.com/watch?v=YY3bTxgxWss. One text tutorial to but i don't get how to make a variable that can take a
"nameOfVar"{}
If it's not a dictionary. The tutorial are using a var nameOfVar: [what should be here in this case] for one that nearly looks like it. The thing is though that theirs are starting with a [{ and ends with a }] while mine only starts with a {? i don't know how to solve this?
Creating corresponding Swift data types for JSON is very easy.
A dictionary {} can be decoded into a class / struct where the keys become properties / members.
An array [] can be decoded into an array of the given (decodable) type.
Any value in double quotes is String even "12" or "false".
Numeric floating point values are Double, integer values are Int and true / false is Bool
null is nil
let jsonString = """
{
"name": "Jens",
"time": "11.45",
"date": "2018:04:17",
"differentTimestamps":[""],
"aWholeLotOfnames":{
"name1": "Karl",
"name2": "pär"
}
}
"""
struct Item: Decodable {
let name, time, date: String
let differentTimestamps: [String]
let aWholeLotOfnames: AWholeLotOfnames
}
struct AWholeLotOfnames : Decodable {
let name1, name2 : String
}
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Item.self, from: data)
print(result)
} catch { print(error) }