Why does NSNumber confuse Bool, Int and Double, and how do you read untyped values safely?

2.9.2 · L2 · Interview

Predict first

What does this print?

print(NSNumber(value: 1) as? Bool as Any, NSNumber(value: 2) as? Bool as Any)
  • Optional(true) nil
  • Optional(true) Optional(true)
  • nil nil

Predict first

What does this print?

let items: [Any] = [1, 2.5]
print(items.map { $0 is Int })
  • [true, false]
  • [true, true]
  • [false, false]

Mental model. An NSNumber is a coin with no country stamped on it. You can call it a dollar or a euro, and the cast will play along.

An NSNumber stores a number but forgets its Swift type. NSNumber(value: 1) is simultaneously an Int, a Double and a Bool as far as casts go. Cast it to Bool and 1 and 0 succeed, while 2 gives nil. So a JSON, plist or userInfo Any holding 1 may read back as true, and testing is Bool before is Int gets the wrong branch.

A native Swift Double in an Any is different: (1.0 as Any) is Int is false, because it never went through NSNumber. The robust fix is to decode into a typed model (Codable) instead of poking at Any. If you must inspect untyped values, decide the order of your checks deliberately, and treat NSNull as a separate case.

Only 0 and 1 bridge to Bool. Anything else fails the cast.

These are native Swift numbers, not NSNumbers, so their types are exact and 2.5 is not an Int.

Common traps

  • Checking is Int before is Bool on parsed JSON. A real true matches the integer branch.
  • Assuming as? Bool means “this was a boolean”. It also accepts 1 and 0.

They might follow up with

  • How does JSON true arrive from JSONSerialization? As an NSNumber that also casts to Int 1.
  • Why does Codable avoid this? You declare each property’s type, so the decoder knows what to demand.

Related questions

  • 2.5.5 When would you use JSONSerialization instead of Codable, and what does the untyped route cost?
  • 2.9.1 What is toll-free bridging, and how do you convert between Swift and Foundation types?

← What is toll-free bridging, and how do you convert between Swift and…   What does bridging cost, and where does it show up in a profile? →