Type checks on Int and Bool values are returning incorrectly in Swift 4.2 - swift

I currently have an issue with Swift 4.2 that I wanted to ask for peoples help addressing.
At the moment, I am deserializing a JSON response, which is a dictionary whose keys are of type String and whose values can be of type Int or Bool. A good example is the following:
{
"number_of_likes": 0,
"is_liked": true
}
When I deserialize the object, the JSON response is of type [String: Any], which is expected.
The task:
I need to create an array detailing which keys are of type Bool and set to true.
The problem:
When running the following code using the response highlighted above:
guard let json = json as? [String: Any] else {
return
}
for key in dict.keys {
print("KEY: \(key)")
let value = dict[key]
if value is Int {
print("It is an integer")
}
if value is Bool {
print("It is a bool")
}
}
return strings
The console prints this:
πŸ”΅ t+20.765 KEY: number_of_likes
πŸ”΅ t+20.765 It is an integer
πŸ”΅ t+20.765 It is a bool
πŸ”΅ t+20.765 KEY: is_liked
πŸ”΅ t+20.765 It is an integer
πŸ”΅ t+20.765 It is a bool
As you can see, the 0 Int value is causing the console to print that it is of type Bool and Int, while the true Bool value is causing the console to print that it is of type Int and Bool.
For context, I did find the following question on StackOverflow, but the answer did not work unless I check for type CFBool as opposed to type Bool.
Is it possible to distinguish Bool and Int in Swift?
Can someone please tell me if I am doing something wrong, or if this is an issue with Swift 4.2?
I definitely appreciate the help, so thank you for all who read this.

Sadly, you can't solve this without going down to a pretty low level, using either Core Foundation types or Objective-C #encode strings.
The problem is that, under the covers, Foundation's JSON serialization uses NSNumber to wrap both integers and booleans. So the JSON 0 and the JSON true are both converted to NSNumber objects, and Swift is willing to convert either of those NSNumber objects to an Int or a Bool on request.
However, the JSON booleans are in fact converted to a subclass of NSNumber called __NSCFBoolean, which is the type that a CFBooleanRef (in Swift, CFBoolean) references:
import Foundation
let json = """
{
"number_of_likes": 0,
"is_liked": true
}
"""
let data = json.data(using: .utf8)!
let jso = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
for key in jso.keys {
if let value = jso[key] as? NSNumber {
print("\(key) \(type(of: value)) \(String(cString: value.objCType))")
}
}
Output:
number_of_likes __NSCFNumber q
is_liked __NSCFBoolean c
It's not documented that Foundation JSON serialization decodes JSON booleans to Core Foundation CFBooleans, but it's unlikely to change.
So here's a Core Foundation way to test:
if let isLikedCF = jso["is_liked"] as CFTypeRef?,
CFGetTypeID(isLikedCF) == CFBooleanGetTypeID()
{
print("it's bool")
} else {
print("it's not bool")
}
What we're doing here is converting the value from the JSON dictionary to a CFTypeRef (which is a reference to any Core Foundation type, and everything returned by Foundation's JSONSerialization is toll-free bridged to a Core Foundation type), and then checking whether the Core Foundation object's type ID is the CFBoolean type ID.
Another way to test using Core Foundation is to recognize that there are only two CFBooleanRef values, kCFBooleanTrue and kCFBooleanFalse. You can see if jso["is_liked"] as? NSNumber is identical to one of those two values, using ===:
if let isLikedNumber = jso["is_liked"] as? NSNumber,
isLikedNumber === kCFBooleanTrue || isLikedNumber === kCFBooleanFalse
{
print("it's bool")
} else {
print("it's not bool")
}
You can also test by checking the NSNumber's Objective-C type code. You cast jso["is_liked"] as? NSNumber, ask for its objCType, convert the resulting C-string to a Swift String, and compare it to "c". If so, it's a boolean. Otherwise, it's not.
if let isLikedNumber = jso["is_liked"] as? NSNumber {
if String(cString: isLikedNumber.objCType) == "c" {
print("it's bool")
} else {
print("it's not bool")
}
}
The c comes from #encode(BOOL) (in Objective-C), where BOOL is a typedef of signed char. This is really obscure stuff. I'd recommend going with a Core Foundation test (shown above), since it's easier to understand and better documented.

You should do something like this
import UIKit
var str = """
{
"number_of_likes": 0,
"is_liked": true
}
"""
struct JsonStruct: Decodable {
var boolWithKey: [String: Bool]?
var intWithKey: [String: Int]?
init(from decoder: Decoder) {
guard let container = try? decoder.container(keyedBy: CodingKeys.self) else {
fatalError()
}
for key in container.allKeys {
if let possibleInt = try? container.decode(Int.self, forKey: key) {
intWithKey = [key.stringValue: possibleInt]
}
if let possibleBool = try? container.decode(Bool.self, forKey: key) {
boolWithKey = [key.stringValue: possibleBool]
}
}
print(container.allKeys)
}
struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
}
let jsonData = str.data(using: .utf8)!
let jsonDecoder = JSONDecoder()
let jsonStruct = try! jsonDecoder.decode(JsonStruct.self, from: jsonData)
print("\(jsonStruct.boolWithKey)")
print("\(jsonStruct.intWithKey)")
Output:
CodingKeys(stringValue: "number_of_likes", intValue: nil),
CodingKeys(stringValue: "is_liked", intValue: nil)]
Optional(["is_liked": true])
Optional(["number_of_likes": 0])

Related

How to seamlessly wrap #Published variables with a UserDefaults propertyWrapper

I've been looking for the perfect UserDefaults wrapper that
seamlessly encodes and decodes Data objects from storage
works with #Published
My starting point was this StackOverflow answer, but it uses object:forKey which doesn't work with custom objects (encoding URLs is always non-trivial for me).
My idea is to be able to use it like so:
struct Server: Identifiable, Codable, Equatable, Hashable { /* vars */ }
class ServerPickerViewModel: ObservableObject {
#Published(wrappedValue: Server.defaultServers.first!,
type: Server.self,
key: "currentServer")
var currentServer: Server?
}
To achieve this, I modified the code from #VictorKushnerov's answer:
import Combine
private var cancellables = [String: AnyCancellable]()
extension Published {
init<T: Encodable & Decodable>(wrappedValue defaultValue: T, type: T.Type, key: String) {
let decoder = JSONDecoder()
var value: T
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(T.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value) // <-- Error
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
let encodedVal = encoder.encode(val) // <-- Error
UserDefaults.standard.set(encodedVal, forKey: key)
}
}
}
There are currently two errors I can't get through, which are the following:
Cannot convert value of type 'T' to expected argument type 'Value' it still relying on the underlying #Published's Value generic type, I wish I could override that with my type T.
Instance method 'encode' requires that 'Published<Value>.Publisher.Output' (aka 'Value') conform to 'Encodable'
You can fix your compilation errors by using where Value : Codable to restrict your extension. Then, you can get rid of your T generic altogether (& you don't have to use the type argument either):
extension Published where Value : Codable {
init(wrappedValue defaultValue: Value, key: String) {
let decoder = JSONDecoder()
var value: Value
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(Value.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value)
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
do {
let encodedVal = try encoder.encode(val)
UserDefaults.standard.set(encodedVal, forKey: key)
} catch {
print(error)
assertionFailure(error.localizedDescription)
}
}
}
}
This being said, I'd probably take the path instead of creating a custom property wrapper instead that wraps #AppStorage instead of extending #Published

Swift Invalid type in JSON write error using JSONSerialization.data

I have an array with elements of a custom type. Here is the type :
public class RequestElemDataBody: Codable {
public var name: String
public var value: String
public init(name: String, value: String) {
self.name = name
self.value = value
}
}
This is how I declare my array :
var elementsInForm = [RequestElemDataBody]()
I use a function to convert this array to Data then to String :
func json(from object: [Any]) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: String.Encoding.utf8)
}
When executing I get this error message :
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (Data.RequestElemDataBody)'
I don't know what is wrong with my custom type since it is Codable.
How can I parse my array with my function without it throwing an error ?
You should use JSONEncoder when serializing Codable.
The example from the documentation page:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/
Problem with your approach is that you try to encode the whole array. But your codable object is not an array at the moment.
Try passing single element to your method and return the string to work with it outside;
func json(from element: RequestElemDataBody) -> String {
...
}
As mentioned here the top level object can be Array or Dictionary and all objects can be instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
. In your case they are instances of an custom object.
So you can try converting the objects to dictionary first and then use JSONSerialization
And to convert to dictionary you can create an extension on Encodable
extension Encodable {
var dict : [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else { return nil }
return json
}
}
and while calling function you can use ArrayName.compactMap({$0.dict})
Also you can use ObjectMapper library.
Reference from: How to convert a Swift object to a dictionary

Swift - unwrapping a Double value with a Guard let statement

to unwrap a Double from my default values, I seem to have to do it with two guard let statements to unwrap the value safely such as in what follows:
guard let distanceAwayPreference = self.defaults.string(forKey: "distancePreference") else {
return
}
guard let doubleDAP = Double(distanceAwayPreference) else {
return
}
– because if I do it like this:
guard let DistanceAwayPreference = self.defaults.double(forKey: "distancePreference") else {
return
}
– I get the error:
Initializer for conditional binding must have Optional type, not 'Double'
Is there a better way so i can do it once, or have less code throughout my app?
The method double(forKey:) is not returning an optional at all. According to the documentation it returns:
The double value associated with the specified key. If the key doesnβ€˜t exist, this method returns 0.
So you can't optional bind it to a variable. This is why you've got that error:
Initializer for conditional binding must have Optional type, not 'Double'
For less and cleaner code (when using it), you can use a simple property wrapper like:
#propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
set { UserDefaults.standard.set(newValue, forKey: key) }
}
}
Then you can have a custom helper struct like this:
struct UserDefaultsConfig {
#UserDefault("distancePreference", defaultValue: 0)
static var distancePreference: Double
}
And use it late like:
UserDefaultsConfig.distancePreference = 12
I think it's better to return Any from defaults instead of String. So, this way you can use optional casting.
guard let doubleDAP = self.defaults.object(forKey: "distancePreference") as? Double else {
return
}

Can't extract the dictionary as [String : Any] from vapor request context

I'm new to server side swift. I'm using vapor for server side swift. In vapor request I need to get JSON as [String: Any] to check a data type of the value like String, Int or Float. But in request the I can't find the exact data type of the value.
drop.post("post") { (request) -> ResponseRepresentable in
guard let name = request.data["value"]?.string else {
throw Abort.badRequest
}
return value
}
In above method, it directly converts and returns the value as String. I need to check it is a String or Int (some other data types too). I can't check by let condition which is given as below.
guard let name = data["value"] as? String else {
\\do something
}
I need to check it is a String or Int (some other data types too). If anyone has the solution, please let me know.
if you try to get Type of data using a type(of:) function, in result you will get optional any like this.
let dic = ["key":"string"] as [String : Any]
let str = dic["key"] //"string"
type(of: str) //Optional<Any>.Type
you have to check with " if let " for all case.
let dic = ["key":"string"] as [String : Any]
if let str = dic["key"] as? String {
print("String value")
} else if let intvalue = dic["key"] as? Int {
print("Int value")
} else if let boolvalue = dic["key"] as? Bool {
print("Bool value")
}
Hope it will help you
Since you're accessing the JSON, I'd do it like this.
guard let json = request.json else {
throw ...
}
if let string = json.string {
// JSON is a string
} else if let object = json.object {
// JSON is a [String: JSON]
}
// continue unwrapping for different data types
I wouldn't recommend using the type(of:) function for this purpose.

Using reflection to set object properties without using setValue forKey

In Swift it's not possible use .setValue(..., forKey: ...)
nullable type fields like Int?
properties that have an enum as it's type
an Array of nullable objects like [MyObject?]
There is one workaround for this and that is by overriding the setValue forUndefinedKey method in the object itself.
Since I'm writing a general object mapper based on reflection. See EVReflection I would like to minimize this kind of manual mapping as much as possible.
Is there an other way to set those properties automatically?
The workaround can be found in a unit test in my library here
This is the code:
class WorkaroundsTests: XCTestCase {
func testWorkarounds() {
let json:String = "{\"nullableType\": 1,\"status\": 0, \"list\": [ {\"nullableType\": 2}, {\"nullableType\": 3}] }"
let status = Testobject(json: json)
XCTAssertTrue(status.nullableType == 1, "the nullableType should be 1")
XCTAssertTrue(status.status == .NotOK, "the status should be NotOK")
XCTAssertTrue(status.list.count == 2, "the list should have 2 items")
if status.list.count == 2 {
XCTAssertTrue(status.list[0]?.nullableType == 2, "the first item in the list should have nullableType 2")
XCTAssertTrue(status.list[1]?.nullableType == 3, "the second item in the list should have nullableType 3")
}
}
}
class Testobject: EVObject {
enum StatusType: Int {
case NotOK = 0
case OK
}
var nullableType: Int?
var status: StatusType = .OK
var list: [Testobject?] = []
override func setValue(value: AnyObject!, forUndefinedKey key: String) {
switch key {
case "nullableType":
nullableType = value as? Int
case "status":
if let rawValue = value as? Int {
status = StatusType(rawValue: rawValue)!
}
case "list":
if let list = value as? NSArray {
self.list = []
for item in list {
self.list.append(item as? Testobject)
}
}
default:
NSLog("---> setValue for key '\(key)' should be handled.")
}
}
}
I found a way around this when I was looking to solve a similar problem - that KVO can't set the value of a pure Swift protocol field. The protocol has to be marked #objc, which caused too much pain in my code base.
The workaround is to look up the Ivar using the objective C runtime, get the field offset, and set the value using a pointer.
This code works in a playground in Swift 2.2:
import Foundation
class MyClass
{
var myInt: Int?
}
let instance = MyClass()
// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(instance.dynamicType, "myInt")
let fieldOffset = ivar_getOffset(ivar)
// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(instance)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)
// Set the value using the pointer
pointerToField.memory = 42
assert(instance.myInt == 42)
Notes:
This is probably pretty fragile, you really shouldn't use this.
But maybe it could live in a thoroughly tested and updated reflection library until Swift gets a proper reflection API.
It's not that far away from what Mirror does internally, see the code in Reflection.mm, around here: https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
The same technique applies to the other types that KVO rejects, but you need to be careful to use the right UnsafeMutablePointer type. Particularly with protocol vars, which are 40 or 16 bytes, unlike a simple class optional which is 8 bytes (64 bit). See Mike Ash on the topic of Swift memory layout: https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html
Edit: There is now a framework called Runtime at https://github.com/wickwirew/Runtime which provides a pure Swift model of the Swift 4+ memory layout, allowing it to safely calculate the equivalent of ivar_getOffset without invoking the Obj C runtime. This allows setting properties like this:
let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)
This is probably a good way forward until the equivalent capability becomes part of Swift itself.
Swift 5
To set and get properties values with pure swift types you can use internal ReflectionMirror.swift approach with shared functions:
swift_reflectionMirror_recursiveCount
swift_reflectionMirror_recursiveChildMetadata
swift_reflectionMirror_recursiveChildOffset
The idea is to gain info about an each property of an object and then set a value to a needed one by its pointer offset.
There is example code with KeyValueCoding protocol for Swift that implements setValue(_ value: Any?, forKey key: String) method:
typealias NameFreeFunc = #convention(c) (UnsafePointer<CChar>?) -> Void
struct FieldReflectionMetadata {
let name: UnsafePointer<CChar>? = nil
let freeFunc: NameFreeFunc? = nil
let isStrong: Bool = false
let isVar: Bool = false
}
#_silgen_name("swift_reflectionMirror_recursiveCount")
fileprivate func swift_reflectionMirror_recursiveCount(_: Any.Type) -> Int
#_silgen_name("swift_reflectionMirror_recursiveChildMetadata")
fileprivate func swift_reflectionMirror_recursiveChildMetadata(
_: Any.Type
, index: Int
, fieldMetadata: UnsafeMutablePointer<FieldReflectionMetadata>
) -> Any.Type
#_silgen_name("swift_reflectionMirror_recursiveChildOffset")
fileprivate func swift_reflectionMirror_recursiveChildOffset(_: Any.Type, index: Int) -> Int
protocol Accessors {}
extension Accessors {
static func set(value: Any?, pointer: UnsafeMutableRawPointer) {
if let value = value as? Self {
pointer.assumingMemoryBound(to: self).pointee = value
}
}
}
struct ProtocolTypeContainer {
let type: Any.Type
let witnessTable = 0
var accessors: Accessors.Type {
unsafeBitCast(self, to: Accessors.Type.self)
}
}
protocol KeyValueCoding {
}
extension KeyValueCoding {
private mutating func withPointer<Result>(displayStyle: Mirror.DisplayStyle, _ body: (UnsafeMutableRawPointer) throws -> Result) throws -> Result {
switch displayStyle {
case .struct:
return try withUnsafePointer(to: &self) {
let pointer = UnsafeMutableRawPointer(mutating: $0)
return try body(pointer)
}
case .class:
return try withUnsafePointer(to: &self) {
try $0.withMemoryRebound(to: UnsafeMutableRawPointer.self, capacity: 1) {
try body($0.pointee)
}
}
default:
fatalError("Unsupported type")
}
}
public mutating func setValue(_ value: Any?, forKey key: String) {
let mirror = Mirror(reflecting: self)
guard let displayStyle = mirror.displayStyle
, displayStyle == .class || displayStyle == .struct
else {
return
}
let type = type(of: self)
let count = swift_reflectionMirror_recursiveCount(type)
for i in 0..<count {
var field = FieldReflectionMetadata()
let childType = swift_reflectionMirror_recursiveChildMetadata(type, index: i, fieldMetadata: &field)
defer { field.freeFunc?(field.name) }
guard let name = field.name.flatMap({ String(validatingUTF8: $0) }),
name == key
else {
continue
}
let clildOffset = swift_reflectionMirror_recursiveChildOffset(type, index: i)
try? withPointer(displayStyle: displayStyle) { pointer in
let valuePointer = pointer.advanced(by: clildOffset)
let container = ProtocolTypeContainer(type: childType)
container.accessors.set(value: value, pointer: valuePointer)
}
break
}
}
}
This approach works with both class and struct and supports optional, enum and inherited(for classes) properties:
// Class
enum UserType {
case admin
case guest
case none
}
class User: KeyValueCoding {
let id = 0
let name = "John"
let birthday: Date? = nil
let type: UserType = .none
}
var user = User()
user.setValue(12345, forKey: "id")
user.setValue("Bob", forKey: "name")
user.setValue(Date(), forKey: "birthday")
user.setValue(UserType.admin, forKey: "type")
print(user.id, user.name, user.birthday!, user.type)
// Outputs: 12345 Bob 2022-04-22 10:41:10 +0000 admin
// Struct
struct Book: KeyValueCoding {
let id = 0
let title = "Swift"
let info: String? = nil
}
var book = Book()
book.setValue(56789, forKey: "id")
book.setValue("ObjC", forKey: "title")
book.setValue("Development", forKey: "info")
print(book.id, book.title, book.info!)
// Outputs: 56789 ObjC Development
if you are afraid to use #_silgen_name for shared functions you can access to it dynamically with dlsym e.g.: dlsym(RTLD_DEFAULT, "swift_reflectionMirror_recursiveCount") etc.
UPDATE
There is a swift package (https://github.com/ikhvorost/KeyValueCoding) with full implementation of KeyValueCoding protocol for pure Swift and it supports: get/set values to any property by a key, subscript, get a metadata type, list of properties and more.
Unfortunately, this is impossible to do in Swift.
KVC is an Objective-C thing. Pure Swift optionals (combination of Int and Optional) do not work with KVC. The best thing to do with Int? would be to replace with NSNumber? and KVC will work. This is because NSNumber is still an Objective-C class. This is a sad limitation of the type system.
For your enums though, there is still hope. This will not, however, reduce the amount of coding that you would have to do, but it is much cleaner and at its best, mimics the KVC.
Create a protocol called Settable
protocol Settable {
mutating func setValue(value:String)
}
Have your enum confirm to the protocol
enum Types : Settable {
case FirstType, SecondType, ThirdType
mutating func setValue(value: String) {
if value == ".FirstType" {
self = .FirstType
} else if value == ".SecondType" {
self = .SecondType
} else if value == ".ThirdType" {
self = .ThirdType
} else {
fatalError("The value \(value) is not settable to this enum")
}
}
}
Create a method: setEnumValue(value:value, forKey key:Any)
setEnumValue(value:String forKey key:Any) {
if key == "types" {
self.types.setValue(value)
} else {
fatalError("No variable found with name \(key)")
}
}
You can now call self.setEnumValue(".FirstType",forKey:"types")