Skip to content

Commit 81507fc

Browse files
authored
Merge pull request SwiftGen#786 from SwiftGen/bugfix/json-integer-parsing
JSON: fix 0/1 integer parsing
2 parents 31c8f18 + fb0b2ee commit 81507fc

File tree

64 files changed

+984
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+984
-38
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ _None_
3737
* JSON/YAML: the `inline` templates incorrectly generated `1`/`0` as values, instead of `true`/`false` as expected.
3838
[David Jennes](https://github.com/djbe)
3939
[#783](https://github.com/SwiftGen/SwiftGen/pull/783)
40+
* JSON: the parser now correctly recognizes `0` and `1` as `Int` (instead of `Bool`).
41+
[David Jennes](https://github.com/djbe)
42+
[#786](https://github.com/SwiftGen/SwiftGen/pull/786)
4043

4144
### Internal Changes
4245

Sources/SwiftGenKit/Utils/Metadata.swift

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,43 @@ enum Metadata {
3131
/// for complex types (such as arrays and dictionaries), describing the type information of sub-elements (such as an
3232
/// array's element, or each of a dictionary's properties).
3333
///
34-
/// Note: this is used for the Plist and YAML Stencil contexts
34+
/// Note: this is used for the JSON, Plist and YAML Stencil contexts
3535
///
3636
/// - Parameter data: The value to describe
3737
/// - Returns: Dictionary with type information about the value (for Stencil context)
3838
static func generate(for data: Any) -> [String: Any] {
39-
switch data {
40-
case is String:
39+
let dataType = type(of: data)
40+
41+
// We want to use toll-free briding using `data is X`, to for example easily check if something is a `String` (or
42+
// `String`-like). In other cases we actually want to avoid it, and use `dataType == X.self`. TFB to `NSNumber` for
43+
// example leads to confusion for ambiguous values (0, 1, true, false, ...).
44+
if data is String {
4145
return [Key.type: ValueType.string]
42-
case is Bool:
46+
} else if dataType == Bool.self {
4347
return [Key.type: ValueType.bool]
44-
case is Int:
48+
} else if dataType == Int.self {
4549
return [Key.type: ValueType.int]
46-
case is Double:
50+
} else if dataType == Double.self {
4751
return [Key.type: ValueType.double]
48-
case is Date:
52+
} else if data is Date {
4953
return [Key.type: ValueType.date]
50-
case is Data:
54+
} else if data is Data {
5155
return [Key.type: ValueType.data]
52-
case let data as [Any]:
56+
} else if let data = data as? NSNumber {
57+
return [Key.type: valueType(for: data)]
58+
} else if let data = data as? [Any] {
5359
return [
5460
Key.type: ValueType.array,
5561
Key.element: Metadata.describe(arrayElement: data)
5662
]
57-
case let data as [String: Any]:
63+
} else if let data = data as? [String: Any] {
5864
return [
5965
Key.type: ValueType.dictionary,
6066
Key.properties: Metadata.describe(dictionary: data)
6167
]
62-
case is NSNull, nil:
68+
} else if dataType == NSNull.self || Mirror(reflecting: data).displayStyle == .optional {
6369
return [Key.type: ValueType.optional]
64-
default:
70+
} else {
6571
return [Key.type: ValueType.any]
6672
}
6773
}
@@ -77,12 +83,8 @@ enum Metadata {
7783
private static func describe(arrayElement array: [Any]) -> [String: Any] {
7884
if array is [String] {
7985
return [Key.type: ValueType.string]
80-
} else if array is [Bool] {
81-
return [Key.type: ValueType.bool]
82-
} else if array is [Int] {
83-
return [Key.type: ValueType.int]
84-
} else if array is [Double] {
85-
return [Key.type: ValueType.double]
86+
} else if let array = array as? [NSNumber], let valueType = elementValueType(for: array) {
87+
return [Key.type: valueType]
8688
} else if array is [Date] {
8789
return [Key.type: ValueType.date]
8890
} else if array is [Data] {
@@ -104,4 +106,44 @@ enum Metadata {
104106
]
105107
}
106108
}
109+
110+
/// Get the value type of a number if possible.
111+
///
112+
/// `NSNumber` does not provide an easy way for checking the internal value type. Therefore we first have to check if
113+
/// it's a boolean, and if not try to match the CF type to something Swift-y.
114+
///
115+
/// - Parameter number: The value to describe
116+
/// - Returns: `ValueType` case (may be `any` if no match is found)
117+
private static func valueType(for number: NSNumber) -> String {
118+
if CFGetTypeID(number) == CFBooleanGetTypeID() {
119+
return ValueType.bool
120+
} else {
121+
switch CFNumberGetType(number) {
122+
case .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, .charType, .intType, .longType, .longLongType,
123+
.nsIntegerType:
124+
return ValueType.int
125+
case .float32Type, .float64Type, .floatType, .doubleType, .cgFloatType:
126+
return ValueType.double
127+
default:
128+
return ValueType.any
129+
}
130+
}
131+
}
132+
133+
/// Returns the element value type, if all elements have the same type.
134+
///
135+
/// The problem with `NSNumber` arrays is that they can contain mixed content, such as `[0.1, 2, true]`. Therefore,
136+
/// we have to check the type of each element.
137+
///
138+
/// - Parameter array: The value to describe
139+
/// - Returns: `ValueType` case if array is uniform (`nil` otherwise)
140+
private static func elementValueType(for array: [NSNumber]) -> String? {
141+
let valueTypes = Set(array.map(valueType(for:)))
142+
143+
if valueTypes.count < 2, let elementType = valueTypes.first {
144+
return elementType
145+
} else {
146+
return nil
147+
}
148+
}
107149
}

Sources/SwiftGenKit/Utils/YamsSerialization.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,29 @@ extension NSData: ScalarRepresentable {
5454

5555
extension NSNumber: ScalarRepresentable {
5656
public func represented() -> Node.Scalar {
57-
if let value = Bool(exactly: self) {
58-
return value.represented()
59-
} else if let value = Double(exactly: self), floor(value) != value {
60-
return value.represented()
61-
} else if let value = Int(exactly: self) {
62-
return value.represented()
57+
if CFGetTypeID(self) == CFBooleanGetTypeID() {
58+
return boolValue.represented()
6359
} else {
64-
return Double.nan.represented()
60+
switch CFNumberGetType(self) {
61+
case .sInt8Type:
62+
return int8Value.represented()
63+
case .sInt16Type:
64+
return int16Value.represented()
65+
case .sInt32Type:
66+
return int32Value.represented()
67+
case .sInt64Type:
68+
return int64Value.represented()
69+
case .charType:
70+
return uint8Value.represented()
71+
case .intType, .longType, .longLongType, .nsIntegerType:
72+
return intValue.represented()
73+
case .float32Type, .floatType:
74+
return floatValue.represented()
75+
case .float64Type, .doubleType, .cgFloatType:
76+
return doubleValue.represented()
77+
default:
78+
return Double.nan.represented()
79+
}
6580
}
6681
}
6782
}

Tests/Fixtures/Generated/JSON/inline-swift4/all-customName.swift

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/Fixtures/Generated/JSON/inline-swift4/all-forceFileNameEnum.swift

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/Fixtures/Generated/JSON/inline-swift4/all-publicAccess.swift

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/Fixtures/Generated/JSON/inline-swift4/all.swift

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/Fixtures/Generated/JSON/inline-swift5/all-customName.swift

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/Fixtures/Generated/JSON/inline-swift5/all-forceFileNameEnum.swift

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/Fixtures/Generated/JSON/inline-swift5/all-publicAccess.swift

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)