Cast Any to RawRepresentable where RawValue == String - ios

I am trying to encode data. For the sake of the example I'll use JSON encoding even though there are other solutions for this out there.
I would like to handle enums that are backed by String or an Int as if they were simply String and Int:
struct UnhandledValueError: Error {}
enum Foo: String {
case bar
}
func encode(_ data: Any?) throws -> String {
guard let data = data else {
return "null"
}
if let string = data as? String {
return "\"\(string)\""
}
if let int = data as? Int {
return String(int)
}
// represent String and Int based enums there
if let array = data as? [Any?] {
return try "[\(array.map({ try encode($0) }).joined(separator: ","))]"
}
if let dict = data as? [String: Any?] {
return try "{\(dict.map({ "\"\($0.key)\": \(try encode($0.value))" }).joined(separator: ","))}"
}
throw UnhandledValueError()
}
let value: Any? = ["foo": Foo.bar]
try encode(value)
Because RawRepresentable is a generic protocol I don't think I can do either
if let value = data as? RawRepresentable
or
if let value = data as? RawRepresentable where RawValue == String
How can I access the raw value (as Any?) from an object that might be RawRepresentable ?

You can create a generic method and constrain its type to RawRepresentable where its RawValue conform to LosslessStringConvertible:
func encode<T>(object: T) -> String where T: RawRepresentable, T.RawValue: LosslessStringConvertible {
.init(object.rawValue)
}
Playground testing:
enum IntEnum: Int {
case a = 1, b, c
}
enum StringEnum: String {
case a, b , c
}
let int: IntEnum = .c
let string: StringEnum = .c
encode(object: int) // "3"
encode(object: string) // "c"

Related

Swift property wrapper String to Numeric type

I want to convert a value might be string or number to number finally by property wrapper.
For example:
"12" -> 12
12 -> 12
I achieved it by specific type Int, Double etc. (with below code, change T to specific type):
import Foundation
#propertyWrapper
struct NumericOrString<T: Numeric & Decodable> {
var wrappedValue: T?
init(wrappedValue: T?) {
self.wrappedValue = wrappedValue
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(String.self) {
wrappedValue = T(value) // if T is Double then Double(value) works.
} else if let value = try? container.decode(T.self) {
wrappedValue = value
} else {
wrappedValue = 0
}
}
}
But generic type should be the right direction. So I tried the code above by generic type.
The issue is in this part:
wrappedValue = T(value)
No exact matches in call to initializer .
So what can I do right now is:
if T.self is Double.Type {
wrappedValue = Double(value) as? T
}
if T.self is Int.Type {
wrappedValue = Int(value) as? T
}
Then I have to add many ifs to check the type.
Not sure if Numeric is the right one for both String and Numeric, Any better solutions?
In order for this to work, you need either concrete types to instantiate, or at least a protocol with an initializer requirement for this.
My recommendation would be to go with the latter option, of using a protocol, and push the Numeric requirement to the protocol:
protocol NumericOrStringDecodable: Numeric {
init(numericOrStringContainer: SingleValueDecodingContainer) throws
}
#propertyWrapper struct NumericOrString<T: NumericOrStringDecodable>: Decodable {
var wrappedValue: T
init(from decoder: Decoder) throws {
wrappedValue = try T(numericOrStringContainer: decoder.singleValueContainer())
}
}
Using a protocol will help you a lot, as you'll be able to detect at compile time if the property wrapper was applied to an incompatible type, instead of detecting this at runtime (via a crash):
struct MyStruct {
#NumericOrString var age: Int
#NumericOrString var weight: Double
// the below won't compile
#NumericOrString var visitedCities: [String]
}
Now, coming to the protocol conformance, as others have said in the comments, you only need to support Int or Double if you're decoding from JSON.
extension Int: NumericOrStringDecodable {
init(numericOrStringContainer container: SingleValueDecodingContainer) throws {
if let int = try? container.decode(Int.self) {
self = int
} else if let string = try? container.decode(String.self), let int = Int(string) {
self = int
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
}
}
}
extension Double: NumericOrStringDecodable {
init(numericOrStringContainer container: SingleValueDecodingContainer) throws {
if let double = try? container.decode(Double.self) {
self = double
} else if let string = try? container.decode(String.self), let double = Double(string) {
self = double
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid double value"))
}
}
}

Why does Int enum as dictionary key, produce different json string than Int as dictionary key?

I try to convert a dictionary, with Int enum
enum TypeE: Int, Codable
{
case note = 1
case tab
}
let encoder = JSONEncoder()
let dictionary0 = [TypeE.note:"VALUE0", TypeE.tab:"VALUE1"]
var data = try encoder.encode(dictionary0)
var string = String(data: data, encoding: .utf8)!
// [1,"VALUE0",2,"VALUE1"]
print(string)
The produced json string output is
[1,"VALUE0",2,"VALUE1"]
It looks strange to me. As, the produced json string represents an array.
If I tested with
let encoder = JSONEncoder()
let dictionary1 = [1:"VALUE0", 2:"VALUE1"]
var data = try encoder.encode(dictionary1)
var string = String(data: data, encoding: .utf8)!
// {"1":"VALUE0","2":"VALUE1"}
print(string)
The produced json string output is
{"1":"VALUE0","2":"VALUE1"}
It seems like if I use Int enum as dictionary key, the produced json string will become representation of array?
Is there any mistake in my code, or my expectation is incorrect?
The source code of Codable provides explanation for this behavior. If the key isn't a String or an Int, the resulting type is an Array:
public func encode(to encoder: Encoder) throws {
if Key.self == String.self {
// Since the keys are already Strings, we can use them as keys directly.
...
} else if Key.self == Int.self {
// Since the keys are already Ints, we can use them as keys directly.
...
} else {
// Keys are Encodable but not Strings or Ints, so we cannot arbitrarily
// convert to keys. We can encode as an array of alternating key-value
// pairs, though.
var container = encoder.unkeyedContainer()
for (key, value) in self {
try container.encode(key)
try container.encode(value)
}
}
}
I made an extension for your case may be useful:
extension Dictionary where Key == TypeE.RawValue, Value == String {
init(dictionaryLiteral elements: (TypeE, String)...) {
var headers: [TypeE.RawValue: String] = [:]
for pair in elements {
headers[pair.0.rawValue] = pair.1
}
self = headers
}
subscript(typeE t: TypeE) -> Value? {
get {
return self[t.rawValue]
}
set {
self[t.rawValue] = newValue
}
}
}
usage:
var dic2 = Dictionary<TypeE.RawValue, String>.init(dictionaryLiteral: (TypeE.note,"Str"), (TypeE.tab,"Str2"))
add new key/Value:
dic2[typeE: TypeE.thirdCase] = "Str3"
O/P:
and get specific key:

Swift 4 - Convert 1 or 0 from mysql to bool

I have this variable here, its an Any
item["completed"]
Its either 1 or 0 which is true or false from mysql database, how can I covert the 1 or 0 to true or false?
I have tried this:
(item["completed"] as! NSNumber).boolValue)
but I get this error
Could not cast value of type 'NSTaggedPointerString' (0x10f906560) to
'NSNumber' (0x10e455d40).
You can try below extension which i have implemented,
extension String {
var bool: Bool {
return self == "1" ? true : false
}
}
(item["completed"] as? String).bool
Hope this way may help you.
Just use
let isCompleted = "1" == (item["completed"] as? String)
If you are using Decodable for json parsing, you can use following ApiBool struct instead of Bool
struct ApiBool: Codable {
let value: Bool
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self.value = string == "1"
} else {
let intVal = try container.decode(Int.self)
self.value = intVal == 1
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.value ? 1 : 0)
}
}
Note As mentioned in comments, you got an String data type and it is expected to be an integer, this is caused by libmysql driver, you can connect to MySQL using mysqlnd driver instead, this will result in a JSON with proper numeric data types, following posts might help you configure the driver
https://stackoverflow.com/a/46690317/1244597
https://stackoverflow.com/a/45366230/1244597
#ahemadabbas-vagh shared a good and reusable approach. But if we talk about computer science, a string's Boolean equality should not be measured like that.
This approach is the same as his, but it asks Bool if the value is true or false, not to String. Bool should know is a value is true or false, not String.
var str = "1"
extension Bool {
static func from(stringValue str: String) -> Bool {
return str == "1"
}
}
if Bool.from(stringValue: str) {
print("TRUE")
} else {
print("FALSE")
}
But it is still not correct. In computer science, null/nil or/and an empty string should be false and any other strings should be true. You can add the string "0" to this set.
You can implement like this; better approaches still might exist.
The idea is; globally a string can only be converted to true if it is not "0", not "" (empty string), not null/nil or if it is "true".
var str = "1" // check for nil, empty string and zero; these should be false; any other string should be true.
extension Bool {
static func from(stringValue str: String) -> Bool {
if str == "true" {
return true
}
guard let intVal = Int(str) else {
return false
}
return Bool(truncating: intVal as NSNumber)
}
}
if Bool.from(stringValue: str) {
print("TRUE")
} else {
print("FALSE")
}
If you use Decodable protocol; I know you know how to handle but:
struct aStruct: Decodable {
let boolVal: Bool
enum CodingKeys: String, CodingKey {
case completed
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let boolString = try container.decode(String.self, forKey: .completed)
self.boolVal = Bool.from(stringValue: boolString)
}
}

Type 'customDataObject' does not conform to protocol 'Sequence'

What I'm trying to do is retrieve json data(which is in array format) and check to see if my local array already contains the data, if it does move on to the next value in the JSON data until their is a value that the array doesn't contain then append it to the array. This data in the array must be in order. I'm attempting to do this now but get the error:
Type 'ResultsGenrePosters' does not conform to protocol 'Sequence'
This is what it looks like:
public struct ResultsGenrePosters: Decodable {
public let results : [GenrePosters]?
public init?(json: JSON) {
results = "results" <~~ json
}
}
public struct GenrePosters: Decodable {
public let poster : String
public init? (json: JSON) {
guard let poster: String = "poster_path" <~~ json
else {return nil}
self.poster = poster
}
static func updateGenrePoster(genreID: NSNumber, urlExtension: String, completionHandler:#escaping (_ details: [String]) -> Void){
var posterArray: [String] = []
let nm = NetworkManager.sharedManager
nm.getJSONData(type:"genre/\(genreID)", urlExtension: urlExtension, completion: {
data in
if let jsonDictionary = nm.parseJSONData(data)
{
guard let genrePosters = ResultsGenrePosters(json: jsonDictionary)
else {
print("Error initializing object")
return
}
guard let posterString = genrePosters.results?[0].poster
else {
print("No such item")
return
}
for posterString in genrePosters {
if posterArray.contains(posterString){continue
} else { posterArray.append(posterString) } //This is where the error happens
}
}
completionHandler(posterArray)
})
}
}
Alt + click on genrePosters and what does it tell you? It should say its ResultsGenrePosters because thats what the error is saying. Now look at the type of posterArray; its an array of String, not Array ResultsGenrePosters. I think you mean to write for poster in genrePosters and have confused yourself about the types because you wrote for posterString in genrePosters.
Maybe you want to use map to transform genrePosters into a [String] ?
This transforms your posterArray, if it exists into an array containing just the poster names. If it doesn't exist you get an empty array. This only works if poster is String. If its String? you should use flatMap instead.
let posterNames = genrePosters.results?.map { $0.poster } ?? [String]()

flatMap, Struct and JSON in Swift 3

I am having a problem with initializing Struct with JSON Data received from the URLRequest in Swift 3.
The protocol JSONDecodable:
protocol JSONDecodable {
init?(JSON: [String : AnyObject])
}
The struct is as follows and it implements extension that conforms to JSONDecodable:
struct Actor {
let ActorID: String
let ActorName: String
}
extension Actor: JSONDecodable {
init?(JSON: [String : AnyObject]) {
guard let ActorID = JSON["ActorID"] as? String, let ActorName = JSON["ActorName"] as? String else {
return nil
}
self.ActorID = ActorID
self.ActorName = ActorName
}
}
Then I have the following code where I try to map an array of dictionaries to struct Actor. I think that I mix Swift 2 and Swift 3 syntax together.
guard let actorsJSON = json?["response"] as? [[String : AnyObject]] else {
return
}
return actorsJSON.flatMap { actorDict in
return Actor(JSON: actorDict)
}
However, I get the following error: 'flatMap' produces '[SegmentOfResult.Iterator.Element]', not the expected contextual result type 'Void' (aka '()')
Any help would be greatly appreciated!
As #dfri mentioned, you haven't showed us your function signature but I'm also guessing you haven't specified the return type of the function.
This is how I would do it though
typealias JSONDictionary = [String: AnyObject]
extension Actor {
func all(_ json: JSONDictionary) -> [Actor]? {
guard let actorsJSON = json["response"] as? [JSONDictionary] else { return nil }
return actorsJSON.flatMap(Actor.init).filter({ $0 != nil }).map({ $0! })
}
}

Resources