Swift force casting to string - swift

i have some data coming. from an api , any way i created a model to transform json to Object.
the problem is that there is a key in json causing my problem.
this key may return an boolean or an string so i need to force cast it to string "i don't have access to the api"
my code :
enum ISpecial: Codable {
case bool(Bool)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(ISpecial.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ISpecial"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}}
then in the view :
VStack(alignment:.leading){
Text(product.name ?? "No name")
.padding(.vertical,5).font(.system(size: 12)).foregroundColor(Color.black.opacity(0.9))
VStack{
Text(product.price ?? " ").strikethrough()
Text( product.special ?? " ")
}
}

You can add a property to ISpecial that returns a String if it is the string case, and nil otherwise:
var stringValue: String? {
if case .string(let s) = self {
return s
} else {
return nil
}
}
Then you can do:
Text( product.special?.stringValue ?? " ")

Related

Firestore. Swift. Create document with dynamic field

I am new to Firestore. For example I have an Object like this:
struct Item: Codable {
let key: String?
let value: ItemAnyValueType?
}
public indirect enum ItemAnyValueType: Codable {
case string(String)
case bool(Bool)
public init(from decoder: Decoder) throws {
let singleValueContainer = try decoder.singleValueContainer()
if let value = try? singleValueContainer.decode(Bool.self) {
self = .bool(value)
return
} else if let value = try? singleValueContainer.decode(String.self) {
self = .string(value)
return
}
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Error"))
}
private enum CodingKeys: String, CodingKey {
case string
case bool
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .string(let value):
try container.encode(value, forKey: .string)
case .bool(let value):
try container.encode(value, forKey: .bool)
}
}
}
The item in Firestore looks like this:
documents
id: 123123 -> key: "test1", value: true
id: 323412 -> key: "test2", value: "good"
id: xx2332 -> key: "test3", value: "i love cats"
id: 433xxx -> key: "test4", value: "i love dogs"
The field value can be boolean or string.
Example of the saved data:
...
...
...
.document("433xxx")
.setData(from: Item(key: "433xxx", value: .string("i love orange")),
merge: true,
completion: { (error) in
print("ok")
})
but the code saves like this:
id: 433xxx -> key: "433xxx", value: map {string: "i love orange"}
Just like you did in your decoder section, you can use a singleValueContainer:
public indirect enum ItemAnyValueType: Codable {
case string(String)
case bool(Bool)
public init(from decoder: Decoder) throws {
let singleValueContainer = try decoder.singleValueContainer()
if let value = try? singleValueContainer.decode(Bool.self) {
self = .bool(value)
return
} else if let value = try? singleValueContainer.decode(String.self) {
self = .string(value)
return
}
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Error"))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value):
try container.encode(value)
case .bool(let value):
try container.encode(value)
}
}
}

How to initialize a UIKeyboardType with a string?

I have a jsondata struct that is build from a JSON file that contains this :
{
inputFieldTitle: "Total Cost",
keyboardType: "numberPad"
}
In my code, how can I use the attribute keyboardType to initialize the UIKeyboardType enum in order to set the keyboard type on my TextField?
Normally we would do this to get a numberPad keyboard (SwiftUI) :
TextField(jsondata.inputFieldTitle) { ... }
.keyboardType(.numberPad)
But in my scenario, I can't hardcode the keyboardType with .numberPad, I have to use what's specified in the jsondata, how can I use that value to set the keyboardType on the TextField? This obvisouly does not work because the UIKeyboardType is of type Int :
TextField(jsondata.inputFieldTitle) { ... }
.keyboardType(UIKeyboardType(rawValue: jsondata.keyboardType))
Any tips? Thanks.
You can conform UIKeyboardType to Decodable and implement your own custom decoder method:
extension UIKeyboardType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let rawValue = try container.decode(Int.self)
guard let keyboardType = UIKeyboardType(rawValue: rawValue) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "invalid UIKeyboardType rawValue: \(rawValue)")
}
self = keyboardType
} catch DecodingError.typeMismatch {
let string = try container.decode(String.self)
guard let keyboardType = UIKeyboardType(string) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "invalid UIKeyboardType string: \(string)")
}
self = keyboardType
}
}
init?(_ string: String) {
switch string {
case "default": self = .default
case "asciiCapable": self = .asciiCapable
case "numbersAndPunctuation": self = .numbersAndPunctuation
case "URL": self = .URL
case "numberPad": self = .numberPad
case "phonePad": self = .phonePad
case "namePhonePad": self = .namePhonePad
case "emailAddress": self = .emailAddress
case "decimalPad": self = .decimalPad
case "twitter": self = .twitter
case "webSearch": self = .webSearch
case "asciiCapableNumberPad": self = .asciiCapableNumberPad
case "alphabet": self = .alphabet
default: return nil
}
}
}
extension UIKeyboardType: Encodable {
public func encode(to encoder: Encoder) throws {
var encoder = encoder.singleValueContainer()
let string: String
switch self {
case .default: string = "default"
case .asciiCapable: string = "asciiCapable"
case .numbersAndPunctuation: string = "numbersAndPunctuation"
case .URL: string = "URL"
case .numberPad: string = "numberPad"
case .phonePad: string = "phonePad"
case .namePhonePad: string = "namePhonePad"
case .emailAddress: string = "emailAddress"
case .decimalPad: string = "decimalPad"
case .twitter: string = "twitter"
case .webSearch: string = "webSearch"
case .asciiCapableNumberPad: string = "asciiCapableNumberPad"
}
try encoder.encode(string)
}
}
extension DataProtocol {
var string: String? { String(bytes: self, encoding: .utf8) }
}
Playground testing:
struct Test: Codable {
let inputFieldTitle: String
let keyboardType: UIKeyboardType
}
let numberPadJSON = """
{
"inputFieldTitle": "Total Cost",
"keyboardType": "numberPad"
}
"""
do {
let test = try JSONDecoder().decode(Test.self, from: Data(numberPadJSON.utf8))
print(test.keyboardType.rawValue) // 4
let encoded = try JSONEncoder().encode(test)
print(encoded.string ?? "") // {"inputFieldTitle":"Total Cost","keyboardType":"numberPad"}
} catch {
print(error)
}
UIKeyboardType is-a Int, so you cannot use rawValue, because your json value is string.
Here is possible solution
extension UIKeyboardType {
static private let types = ["numberPad": numberPad] // << extend to all supported
init(_ value: String) {
if let result = Self.types[value] {
self = result
} else {
self = .default
}
}
}
and you can now use it as
TextField(jsondata.inputFieldTitle) { ... }
.keyboardType(UIKeyboardType(jsondata.keyboardType))
// .keyboardType(UIKeyboardType(jsondata.keyboardType ?? "")) // if optional

Codable conformance for nested Enum?

I'm a bit tangled up on using Codable with nested enums. Suppose I have the following enum:
enum ItemStatus {
enum Foo {
case a
case b(Double, Double)
case c
}
enum Bar {
case x(String)
case y
case z(Int)
}
}
extension ItemStatus: Codable {
init(from decoder: Decoder) throws {
}
func encode(to encoder: Encoder) throws {
}
}
For normal enums, I'm implementing Codable like this. It works, but I'm not sure how to implement Codable for the top-level enum (in this case, ItemStats), and my gut tells me there is a more efficient way to write this anyway:
extension ItemStatus.Bar {
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "x": self = .x("")
case "y": self = .y
case "z": self = .z(0)
default: return nil
}
}
private var typeStr: String {
guard let label = Mirror(reflecting: self).children.first?.label else {
return .init(describing: self)
}
return label
}
}
extension ItemStatus.Bar : Codable {
private enum CodingKeys: String, CodingKey {
case type
case xVal
case zVal
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let type = try? container.decode(String.self, forKey: .type) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Could not get type of Bar."))
}
guard let base = ItemStatus.Bar(rawValue: type) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Could not init Bar with value: \(type)"))
}
switch base {
case .x:
let val = try container.decode(String.self, forKey: .xVal)
self = .x(val)
case .z:
let val = try container.decode(Int.self, forKey: .zVal)
self = .z(val)
default:
self = base
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.typeStr, forKey: .type)
switch self {
case .x(let val):
try container.encode(val, forKey: .xVal)
case .z(let val):
try container.encode(val, forKey: .zVal)
default: ()
}
}
}

Swift Codable how to use Any type?

When I try to access the value of "value" for example to use it in a label.text, I get an error
Cannot assign value of type 'MyValue?' to type 'String?'
When I print the value to the terminal, it says ...
unknown context at 0x109d06188).MyValue.string...
How can solve this problem?
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)
}
}
}
You can get string values for your label by conforming to rawRepresentable protocol:
enum MyValue: Codable, RawRepresentable {
var rawValue: String {
switch self {
case .string(let stringVal):
return stringVal
case .innerItem(let myVal):
return String(describing: myVal)
}
}
typealias RawValue = String
init?(rawValue: String) {
return nil
}
case string(String)
case innerItem(InnerItem)
}
let myVal = MyValue.string("testString")
var strVal: String = myVal.rawValue // testString
To get the associated values in the enum you could add two computed properties in MyValue
var stringValue : String? {
guard case .string(let string) = self else { return nil }
return string
}
var innerItemValue : InnerItem? {
guard case .innerItem(let innerItem) = self else { return nil }
return innerItem
}
Or switch on value like in the encode method
switch root.value {
case .string(let string): // do something with `string`
case .innerItem(let innerItem): // do something with `innerItem`
}
Or simply use if case
if case .string(let string) = root.value { someLabel.text = string }

How to parse JSON with Decodable protocol when property types might change from Int to String? [duplicate]

This question already has answers here:
Using codable with value that is sometimes an Int and other times a String
(5 answers)
Closed 6 months ago.
I have to decode a JSON with a big structure and a lot of nested arrays.
I have reproduced the structure in my UserModel file, and it works, except with one property (postcode) that is in a nested array (Location) that sometimes is an Int and some other is a String. I don't know how to handle this situation and tried a lot of different solutions.
The last one I've tried is from this blog https://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/
And it suggests using generics. But now I can't initialize the Location object without providing a Decoder():
Any help or any different approach would be appreciated.
The API call is this one: https://api.randomuser.me/?results=100&seed=xmoba
This is my UserModel File:
import Foundation
import UIKit
import ObjectMapper
struct PostModel: Equatable, Decodable{
static func ==(lhs: PostModel, rhs: PostModel) -> Bool {
if lhs.userId != rhs.userId {
return false
}
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.body != rhs.body {
return false
}
return true
}
var userId : Int
var id : Int
var title : String
var body : String
enum key : CodingKey {
case userId
case id
case title
case body
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: key.self)
let userId = try container.decode(Int.self, forKey: .userId)
let id = try container.decode(Int.self, forKey: .id)
let title = try container.decode(String.self, forKey: .title)
let body = try container.decode(String.self, forKey: .body)
self.init(userId: userId, id: id, title: title, body: body)
}
init(userId : Int, id : Int, title : String, body : String) {
self.userId = userId
self.id = id
self.title = title
self.body = body
}
init?(map: Map){
self.id = 0
self.title = ""
self.body = ""
self.userId = 0
}
}
extension PostModel: Mappable {
mutating func mapping(map: Map) {
id <- map["id"]
title <- map["title"]
body <- map["body"]
userId <- map["userId"]
}
}
Well it's a common IntOrString problem. You could just make your property type an enum that can handle either String or Int.
enum IntOrString: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(IntOrString.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Int or String)"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
As I have found mismatch of your model that you posted in your question and the one in the API endpoint you pointed to, I've created my own model and own JSON that needs to be decoded.
struct PostModel: Decodable {
let userId: Int
let id: Int
let title: String
let body: String
let postCode: IntOrString
// you don't need to implement init(from decoder: Decoder) throws
// because all the properties are already Decodable
}
Decoding when postCode is Int:
let jsonData = """
{
"userId": 123,
"id": 1,
"title": "Title",
"body": "Body",
"postCode": 9999
}
""".data(using: .utf8)!
do {
let postModel = try JSONDecoder().decode(PostModel.self, from: jsonData)
if case .int(let int) = postModel.postCode {
print(int) // prints 9999
} else if case .string(let string) = postModel.postCode {
print(string)
}
} catch {
print(error)
}
Decoding when postCode is String:
let jsonData = """
{
"userId": 123,
"id": 1,
"title": "Title",
"body": "Body",
"postCode": "9999"
}
""".data(using: .utf8)!
do {
let postModel = try JSONDecoder().decode(PostModel.self, from: jsonData)
if case .int(let int) = postModel.postCode {
print(int)
} else if case .string(let string) = postModel.postCode {
print(string) // prints "9999"
}
} catch {
print(error)
}
You can use generic like this:
enum Either<L, R> {
case left(L)
case right(R)
}
extension Either: Decodable where L: Decodable, R: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let left = try? container.decode(L.self) {
self = .left(left)
} else if let right = try? container.decode(R.self) {
self = .right(right)
} else {
throw DecodingError.typeMismatch(Either<L, R>.self, .init(codingPath: decoder.codingPath, debugDescription: "Expected either `\(L.self)` or `\(R.self)`"))
}
}
}
extension Either: Encodable where L: Encodable, R: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .left(left):
try container.encode(left)
case let .right(right):
try container.encode(right)
}
}
}
And then declare postcode: Either<Int, String> and if your model is Decodable and all other fields are Decodable too no extra code would be needed.
If postcode can be both String and Int, you have (at least) two possible solutions for this issue. Firstly, you can simply store all postcodes as String, since all Ints can be converted to String. This seems like the best solution, since it seems highly unlikely that you'd need to perform any numeric operations on a postcode, especially if some postcodes can be String. The other solution would be creating two properties for postcode, one of type String? and one of type Int? and always only populating one of the two depending on the input data, as explained in Using codable with key that is sometimes an Int and other times a String.
The solution storing all postcodes as String:
struct PostModel: Equatable, Decodable {
static func ==(lhs: PostModel, rhs: PostModel) -> Bool {
return lhs.userId == rhs.userId && lhs.id == rhs.id && lhs.title == rhs.title && lhs.body == rhs.body
}
var userId: Int
var id: Int
var title: String
var body: String
var postcode: String
enum CodingKeys: String, CodingKey {
case userId, id, title, body, postcode
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.userId = try container.decode(Int.self, forKey: .userId)
self.id = try container.decode(Int.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title)
self.body = try container.decode(String.self, forKey: .body)
if let postcode = try? container.decode(String.self, forKey: .postcode) {
self.postcode = postcode
} else {
let numericPostcode = try container.decode(Int.self, forKey: .postcode)
self.postcode = "\(numericPostcode)"
}
}
}
try this extension
extension KeyedDecodingContainer{
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String?{
if let resStr = try? decode(type, forKey: key){
return resStr
}else{
if let resInt = try? decode(Int.self, forKey: key){
return String(resInt)
}
return nil
}
}
public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int?{
if let resInt = try? decode(type, forKey: key){
return resInt
}else{
if let resStr = try? decode(String.self, forKey: key){
return Int(resStr)
}
return nil
}
}
}
example
struct Foo:Codable{
let strValue:String?
let intValue:Int?
}
let data = """
{
"strValue": 1,
"intValue": "1"
}
""".data(using: .utf8)
print(try? JSONDecoder().decode(Foo.self, from: data!))
it will print "Foo(strValue: Optional("1"), intValue: Optional(1))"