Issue with Enum values with Space | Swift - swift

Essentially I am decoding a JSON object with keys that could only be a few different values.
struct People: Decodable {
var name: String
var grade: String
var code: PersonID
enum PersonCodes: String, Decodable {
case In_Transit = "0",
Accepted = "1",
Exception = "2",
Delivered = "3"
}
}
The codes values in the JSON are numbers presented as Strings like "0", "1", "2" etc..
Each code has a meaning like In_Transit, Hired, Ready, All Set .. how can codes be outputed with spaces if enums does not allow spaces (I need to replace the _ with space).
Example of JSON:
{
"name" : "Jake",
"grade" : "A Grade"
"code" : "0"
}
Need for code 0 to be read as "In Transit"

you could try this approach (works for me):
struct People: Decodable {
var name: String
var grade: String
var code: PersonCodes
enum CodingKeys: String, CodingKey {
case name, grade, code
}
enum PersonCodes: String, Decodable {
case In_Transit = "0",
Accepted = "1",
Exception = "2",
Delivered = "3"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
grade = try container.decode(String.self, forKey: .grade)
let stringCode = try container.decode(String.self, forKey: .code)
if let dasCode = PersonCodes(rawValue: stringCode) {
code = dasCode
} else {
code = PersonCodes.In_Transit // <-- todo, pick a default
}
}
}
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
let json = """
{
"name" : "Jake",
"grade" : "A Grade",
"code" : "1"
}
"""
let data = json.data(using: .utf8)!
do {
let decoded = try JSONDecoder().decode(People.self, from: data)
print(decoded)
print(decoded.code)
print(decoded.code.rawValue)
} catch {
print("\(error)")
}
}
}
}
You could also use this, if you want a string description of the code (which is a String not an Int in the json data):
struct People: Decodable {
var name: String
var grade: String
var code: String
enum CodingKeys: String, CodingKey {
case name, grade, code
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
grade = try container.decode(String.self, forKey: .grade)
let stringCode = try container.decode(String.self, forKey: .code)
switch stringCode {
case "0": code = "In Transit"
case "1": code = "Accepted"
case "2": code = "Exception"
case "3": code = "Delivered"
default: code = "Unknown" // <-- todo default
}
}
}

Related

How to decode JSON array as property of struct with JSONDecoder

I had data struct like this
{
"version": 1,
"profile": [
{
"type": "name",
"value": "Hellow"
},
{
"type": "email",
"value": "1#a.com"
},
]
}
Now I could decode it like this
struct Profile: Decodable {
let version: Int
let name: String
let email: String
struct Item: Decodable {
let type: String
let value: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
version = try container.decode(Int.self, forKey: .version)
let items = try container.decode([Item].self, forKey: .data)
name = items.first{$0.type == "name"}!.value
email = items.first{$0.type == "email"}!.value
}
}
Does there are any way to update Profile without rewrite init(from)?
Update
#dynamicMemberLookup can use like mapping function.
But it will lose autocomplete.
#dynamicMemberLookup
struct Profile: Decodable {
let version: Int
struct Item: Decodable {
let type: Keys
let value: String
}
let profile: [Item]
enum Keys: String, Decodable, ExpressibleByStringLiteral {
case name
case email
case undefined
init(stringLiteral: String) {
self = Keys(rawValue: stringLiteral) ?? .undefined
}
}
subscript(dynamicMember member: Keys) -> String {
return profile.first{$0.type == member}?.value ?? "undefined"
}
}

Codable with Generics Swift 5

I am creating a generic post body for my API's call, my post body is mostly same except the data parameter which is different for different API calls data acts like filler for different requirements, few below JSON post body.
Example 1:
{
"timeStampUtc": "2019-07-29T03:29:21.729Z",
...
"geoLocationInfo": {
"latitude": 0,
"longitude": 0,
...
},
"data": {
"loginIdentity": "string",
"loginPassword": "string"
}
}
Example 2:
{
"timeStampUtc": "2019-07-29T03:29:21.729Z",
...
"geoLocationInfo": {
"latitude": 0,
"longitude": 0,
...
},
"data": {
"wazId": 0,
"regionId": 0
}
}
Example 3:
{
"timeStampUtc": "2019-07-29T03:29:21.729Z",
...
"geoLocationInfo": {
"latitude": 0,
"longitude": 0,
...
},
"data": {
"loginIdentity": "string",
"wazID": 0
}
}
I am using the encodable and generics to overcome this requirement, well I am able to do the first two scenarios, but struggle with the third when the data has values of different types.
Following is the sample code can be directly tried in Playground
struct PostBody<T : Codable>: Codable
{
var deviceInfo = ""
var geoLocationInfo = ""
var data = Dictionary<String, T>()
enum CodingKeys: String, CodingKey
{
case deviceInfo, geoLocationInfo, data
}
init(dataDict : Dictionary<String, T>) {
self.data = dataDict
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy : CodingKeys.self)
deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
data = try container.decode(Dictionary.self, forKey: .data)
}
func encode(to encoder : Encoder)
{
var container = encoder.container(keyedBy : CodingKeys.self)
do
{
try container.encode(deviceInfo, forKey : .deviceInfo)
try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
try container.encode(data, forKey : .data)
}
catch
{
fatalError("Should never happen")
}
}
}
let postBody = PostBody<String>(dataDict : ["1" : "1", "2" : "2"])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(postBody)
let encodedDataDict2 = try encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
let postBody1 = PostBody(dataDict : ["1" : 1, "2" : 2])
let encoder1 = JSONEncoder()
encoder1.outputFormatting = .prettyPrinted
try encoder1.encode(postBody1)
let encodedDataDict3 = try encoder1.encode(postBody1)
print(String(data : encodedDataDict3, encoding : .utf8)!)
Instead of generics use an enum for the different types. Feel free to add more types
enum StringOrInt : Codable {
case string(String), integer(Int)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let stringValue = try container.decode(String.self)
self = .string(stringValue)
} catch DecodingError.typeMismatch {
let integerValue = try container.decode(Int.self)
self = .integer(integerValue)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let stringValue): try container.encode(stringValue)
case .integer(let integerValue): try container.encode(integerValue)
}
}
}
struct PostBody: Codable
{
let deviceInfo, geoLocationInfo : String
let data : Dictionary<String, StringOrInt>
}
let postBody = PostBody(deviceInfo: "Foo", geoLocationInfo: "Bar", data : ["loginIdentity" : .string("string"), "wazID" : .integer(0)])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodedDataDict2 = try encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
Swift 5.7.1 EDIT
it just works now:
struct PostBody<T: Codable>: Codable
{
var deviceInfo = ""
var geoLocationInfo = ""
var data = Dictionary<String, T>()
enum CodingKeys: String, CodingKey
{
case deviceInfo, geoLocationInfo, data
}
init(dataDict : Dictionary<String, T>) {
self.data = dataDict
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy : CodingKeys.self)
deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
data = try container.decode(Dictionary<String, T>.self, forKey: .data)
}
func encode(to encoder : Encoder)
{
var container = encoder.container(keyedBy : CodingKeys.self)
do
{
try container.encode(deviceInfo, forKey : .deviceInfo)
try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
try container.encode(data, forKey : .data)
}
catch
{
fatalError("Should never happen")
}
}
}
let postBody = PostBody<String>(dataDict : ["1" : "1", "2" : "2"])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(postBody)
let encodedDataDict2 = try encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
// {
// "geoLocationInfo" : "",
// "deviceInfo" : "",
// "data" : {
// "2" : "2",
// "1" : "1"
// }
// }
let postBody1 = PostBody(dataDict : ["1" : 1, "2" : 2])
let encoder1 = JSONEncoder()
encoder1.outputFormatting = .prettyPrinted
try encoder1.encode(postBody1)
let encodedDataDict3 = try encoder1.encode(postBody1)
print(String(data : encodedDataDict3, encoding : .utf8)!)
// {
// "geoLocationInfo" : "",
// "deviceInfo" : "",
// "data" : {
// "2" : 2,
// "1" : 1
// }
// }
Whenever working with json data I recommend using QuickType as this will allow you to quickly get an idea or to simply just generate code necessary for the different languages that you need.
This is an example based on the data you provided.
There are several options to play with, such as changing between Class or Struct and only using plain types. There is also the option of generating initializers and mutators.

Swift Codable multiple types

I try to parse an api returning a json object. My problem is that some keys are sometime a string, sometime an object like the key "Value" in the following example:
[
{
"Description": null,
"Group": "Beskrivning av enheten",
"GroupDescription": null,
"Id": "Description",
"Name": "Mer om enheten",
"Value": "Det finns möjlighet till parkering på gatorna runt om, men det är kantstenar och ganska branta backar för att komma upp till lekplatsen.\r\n\r\nUtanför själva lekplatsen finns en gungställning med en plan omväg in. Alla lekredskap står i sandytor, det finns många kanter. Runt hela lekplatsen går ett staket med öppningar i olika riktningar."
},
{
"Description": null,
"Group": "Bilder och film",
"GroupDescription": null,
"Id": "Image",
"Name": "Huvudbild",
"Value": {
"__type": "FileInfo",
"Id": "8871b3b1-14f4-4054-8728-636d9da21ace",
"Name": "ullerudsbacken.jpg"
}
}
]
My struct looks like this:
struct ServiceUnit: Codable {
let description: String?
let group: String?
let groupDescription: String?
let id: String
let name: String
var value: String?
struct ServiceUnitTypeInfo: Codable {
let id: String
let singularName: String?
enum CodingKeys: String, CodingKey {
case id = "Id"
case singularName = "SingularName"
}
}
let serviceUnitTypeInfo: ServiceUnitTypeInfo?
let values: [String]?
enum CodingKeys: String, CodingKey {
case description = "Description"
case group = "Group"
case groupDescription = "GroupDescription"
case id = "Id"
case name = "Name"
case value = "Value"
case serviceUnitTypeInfo = "ServiceUnitTypeInfo"
case values = "Values"
case image = "Image"
}
}
I have to admin that I am totally lost (yes, I am a beginner in swift) and I can't find a solution to my problem. I understand that I have to use a custom init, but I don't know how.
You can try
struct Root: Codable {
let description,id: String
let group,groupDescription: String?
let name: String
let value: MyValue
enum CodingKeys: String, CodingKey {
case description = "Description"
case group = "Group"
case groupDescription = "GroupDescription"
case id = "Id"
case name = "Name"
case value = "Value"
}
}
enum MyValue: Codable {
case string(String)
case innerItem(InnerItem)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(InnerItem.self) {
self = .innerItem(x)
return
}
throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let x):
try container.encode(x)
case .innerItem(let x):
try container.encode(x)
}
}
}
struct InnerItem: Codable {
let type, id, name: String
enum CodingKeys: String, CodingKey {
case type = "__type"
case id = "Id"
case name = "Name"
}
}
do {
let result = try JSONDecoder().decode([Root].self,from:data)
print(result)
}
catch {
print(error)
}
Building on the answer of #Sh_Khan and to answer the question of #Nikhi in the comments (how can you access the values) I like to do add this to the enum declaration:
var innerItemValue: InnerItem? {
switch self {
case .innerItem(let ii):
return ii
default:
return nil
}
}
var stringValue: String? {
switch self {
case .string(let s):
return s
default:
return nil
}
}

Codable. How decode dictionary to property [duplicate]

How does the Swift 4 Decodable protocol cope with a dictionary containing a key whose name is not known until runtime? For example:
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
Here we have an array of dictionaries; the first has keys categoryName and Trending, while the second has keys categoryName and Comedy. The value of the categoryName key tells me the name of the second key. How do I express that using Decodable?
The key is in how you define the CodingKeys property. While it's most commonly an enum it can be anything that conforms to the CodingKey protocol. And to make dynamic keys, you can call a static function:
struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}
var name: String
var detail: Detail
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}
init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}
Usage:
let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!
let categories = try! JSONDecoder().decode([Category].self, from: jsonData)
(I changed isFavourit in the JSON to isFavourite since I thought it was a mispelling. It's easy enough to adapt the code if that's not the case)
You can write a custom struct that functions as a CodingKeys object, and initialize it with a string such that it extracts the key you specified:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
Thus, once you know what the desired key is, you can say (in the init(from:) override:
let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
So what I ended up doing is making two containers from the decoder — one using the standard CodingKeys enum to extract the value of the "categoryName" key, and another using the CK struct to extract the value of the key whose name we just learned:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here, then, is my entire Decodable struct:
struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}
And here's the test bed:
let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)
And here's the output of the print statement, proving that we've populated our structs correctly:
[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]
Of course in real life we'd have some error-handling, no doubt!
EDIT Later I realized (in part thanks to CodeDifferent's answer) that I didn't need two containers; I can eliminate the CodingKeys enum, and my CK struct can do all the work! It is a general purpose key-maker:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here's what I eventually came up for this json:
let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!
We make such a structure:
struct Pair {
let name: String
let details: Details
struct Details: Codable {
let last, percentChange, baseVolume: String
}
}
then decode:
if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {
var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}
print(pairs)
}
It is also possible to call not pair.details.baseVolume, but pair.baseVolume:
struct Pair {
......
var baseVolume: String { return details.baseVolume }
......
Or write custom init:
struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......

Codable, Decodable only a value out of Dictionary

I have a json response of an API. This returns a also value, which is a Dictionary. How to can I achieve to store / map only the value of this Dictionary. Here is an example which can be simply put into a playground:
id = ["$oid": "591ae6cb9d1fa2b6e47edc33"]
should only be
id = "591ae6cb9d1fa2b6e47edc33"
Here is an example which can be simply put into a playground:
import Foundation
struct Location : Decodable {
enum CodingKeys : String, CodingKey {
case id = "_id"
}
var id : [String:String]? // this should be only a string with the value of "$oid"
}
extension Location {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode([String:String]?.self, forKey: .id)
}
}
var json = """
[
{
"_id": {
"$oid": "591ae6cb9d1fa2b6e47edc33"
}
},
{
"_id": {
"$oid": "591ae6cd9d1fa2b6e47edc34"
}
}
]
""".replacingOccurrences(of: "}\n{", with: "}\\n{").data(using: .utf8)!
let decoder = JSONDecoder()
do {
let locations = try decoder.decode([Location].self, from: json)
locations.forEach { print($0) }
} catch {
print(error.localizedDescription )
}
You were almost there:
struct Location {
var id: String
}
extension Location: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "_id"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let _id = try values.decode([String: String].self, forKey: .id)
id = _id["$oid"]!
}
}
If you have mote keys under _id in the JSON data, I'd strongly suggest you make a private struct that represents the struct for the benefit of type safety.