-1

I have a Codable object that I need to convert to Dictionary so I first encode it to convert it to Data and then use JSONSerialization to convert that Data to Any and then use as? [String: Any] to get the dictionary.

The conversion is successful but due to use of JSONSerialisation, Bool types are being converted to NSNumber but I want to retain the Bool value inside the Dictionary

import Foundation

struct Employee: Codable {
    let employeeID: Int?
    let meta: Meta?
}

struct Meta: Codable {
    let demo: Bool?
}

let employeeObject = Employee(employeeID: 1, meta: Meta(demo: true))

do {
    let encodedObject = try JSONEncoder().encode(employeeObject)

    let dictionary = try JSONSerialization.jsonObject(with: encodedObject, options: .fragmentsAllowed) as? [String: Any]

    print(dictionary ?? [:])

} catch  {
    print(error)
}

OUTPUT

["meta": { demo = 1; }, "employeeID": 1]

demo property is being converted to NSNumber but I want to retain the Bool value

user121095
  • 697
  • 2
  • 6
  • 18
  • 1
    Possible duplicate of: [Avoid JSONSerialization to convert Bool to NSNumber](https://stackoverflow.com/questions/59420274/avoid-jsonserialization-to-convert-bool-to-nsnumber) – TylerP Dec 20 '19 at 07:36
  • @TylerTheCompiler That question was asked by me as well but it's completely different from this one. The only similarity was that `JSONSerialization` converted `Bool` to `NSNumber` there as well – user121095 Dec 20 '19 at 07:38
  • I fail to see how this is any different than that question. What is the difference? – TylerP Dec 20 '19 at 09:37
  • @TylerTheCompiler In that question I had a `Dictionary` which I wanted to convert to a Decodable object. Here I have an Object that I want to convert to `Dictionary` – user121095 Dec 20 '19 at 09:40
  • That's... not what I see? In both questions you are converting a `Data` to a dictionary, and in both questions you are asking how to prevent this conversion from turning a `Bool` into an `NSNumber`. I don't see anything in that question about wanting to take a dictionary and convert it into a `Decodable`. – TylerP Dec 20 '19 at 09:49

2 Answers2

2

The print output is misleading. That's related to internal implicit bridging to NSDictionary

Although the value is printed as 1 the type is a boolean, see

let meta = dictionary!["meta"] as! [String:Any]
print(type(of: meta["demo"]! )) // __NSCFBoolean

Your concern is causeless because to get the value from a [String:Any] dictionary you have to cast the type anyway

let demo = meta[demo] as! Bool

Or you have to cast the dictionary to [String:Bool]

let meta = dictionary!["meta"] as! [String:Bool]
let demo = meta["demo"]!

Side note:

Declare struct members non-optional as much as possible, not the opposite.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • See my main concern is that I save this inside CoreData and then fetch it back and decode it. Say this *demo* value is converted to `1` from `true` and saved inside CoreData for `demo`. When I fetch this data back from CoreData and feed it to the `Decoder` using `JSONDecoder` with model as `Employee`, it checks the type of *demo*, which as per model is `Bool` but as per dictionary received is `1` so type mismatch occurs and it fails – user121095 Dec 20 '19 at 08:27
  • I don't understand. Please give a concrete example of the type mismatch failure. Once again: `print` outputs are not significant. – vadian Dec 20 '19 at 08:33
1

This should do the trick:

struct Meta: Codable {
    let demo: Bool?

    public init(demo : Bool) {
        self.demo = demo
    }

    enum CodingKeys: String, CodingKey {
        case demo = "demo"
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        demo = try container.decode(Bool.self, forKey: .demo)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.demo, forKey: .demo)
    }
}

The JSON representation is still a number, but it decodes correctly back into Bool.

  • 1
    Consider that everything but the declaration line is synthesized so you can delete it. `struct Meta: Codable { let demo: Bool? }` does exactly the same so actually it makes no difference. – vadian Dec 20 '19 at 09:25