diff --git a/CHANGELOG.md b/CHANGELOG.md index a5aad8bb..4f3ecf92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,14 @@ ##### Breaking -* None. +* Change how empty strings are decoded into nullable properties. + `key: ""` previously decoded into + `struct Value: Codable { let key: String? }` as `Value(key: nil)` + whereas after this change it decodes as `Value(key: "")`. + This could be a breaking change if you were relying on the previous + semantics. + [Liam Nichols](https://github.com/liamnichols) + [#301](https://github.com/jpsim/Yams/issues/301) ##### Enhancements diff --git a/Sources/Yams/Constructor.swift b/Sources/Yams/Constructor.swift index 29aa3c59..41b8a587 100644 --- a/Sources/Yams/Constructor.swift +++ b/Sources/Yams/Constructor.swift @@ -396,6 +396,10 @@ extension NSNull/*: ScalarConstructible*/ { /// /// - returns: An instance of `NSNull`, if one was successfully extracted from the scalar. public static func construct(from scalar: Node.Scalar) -> NSNull? { + // When constructing from a Scalar, only plain style scalars should be recognized. + // For example #"key: 'null'"# or #"key: ''"# should not be considered as null. + guard case .plain = scalar.style else { return nil } + switch scalar.string { case "", "~", "null", "Null", "NULL": return NSNull() diff --git a/Tests/YamsTests/ConstructorTests.swift b/Tests/YamsTests/ConstructorTests.swift index a04cc3cb..9f93039a 100644 --- a/Tests/YamsTests/ConstructorTests.swift +++ b/Tests/YamsTests/ConstructorTests.swift @@ -210,14 +210,16 @@ class ConstructorTests: XCTestCase { // swiftlint:disable:this type_body_length english: null ~: null key --- - # This sequence has five - # entries, two have values. + # This sequence has seven + # entries, four have values. sparse: - ~ - 2nd entry - - 4th entry - Null + - 'null' + - '' """ let objects = Array(try Yams.load_all(yaml: example)) @@ -235,7 +237,9 @@ class ConstructorTests: XCTestCase { // swiftlint:disable:this type_body_length "2nd entry", NSNull(), "4th entry", - NSNull() + NSNull(), + "null", + "" ] as [Any] ] ] diff --git a/Tests/YamsTests/NodeTests.swift b/Tests/YamsTests/NodeTests.swift index 5257e0a2..933de986 100644 --- a/Tests/YamsTests/NodeTests.swift +++ b/Tests/YamsTests/NodeTests.swift @@ -59,7 +59,7 @@ class NodeTests: XCTestCase { let scalarFloat: Node = "1.0" XCTAssertEqual(scalarFloat.float, 1.0) - let scalarNull: Node = "null" + let scalarNull = Node("null", .implicit, .plain) XCTAssertEqual(scalarNull.null, NSNull()) let scalarInt: Node = "1" diff --git a/Tests/YamsTests/TopLevelDecoderTests.swift b/Tests/YamsTests/TopLevelDecoderTests.swift index b6bdd37d..718b1b19 100644 --- a/Tests/YamsTests/TopLevelDecoderTests.swift +++ b/Tests/YamsTests/TopLevelDecoderTests.swift @@ -32,5 +32,51 @@ class TopLevelDecoderTests: XCTestCase { ) XCTAssertEqual(foo?.name, "Bird") } + + func testDecodeOptionalTypes() throws { + let yaml = """ + AAA: '' + BBB: + CCC: null + DDD: ~ + EEE: "" + json: { + "FFF": "", + "GGG": "null" + } + array: + - one + - '' + - null + - 'null' + - '~' + """ + + struct Container: Codable, Equatable { + struct JSON: Codable, Equatable { + var FFF: String? + var GGG: String? + } + + var AAA: String? + var BBB: String? + var CCC: Int? + var DDD: String? + var EEE: String? + var json: JSON + var array: [String?] + } + + let container = try YAMLDecoder().decode(Container.self, from: yaml) + + XCTAssertEqual(container.AAA, "") + XCTAssertEqual(container.BBB, nil) + XCTAssertEqual(container.CCC, nil) + XCTAssertEqual(container.DDD, nil) + XCTAssertEqual(container.EEE, "") + XCTAssertEqual(container.json.FFF, "") + XCTAssertEqual(container.json.GGG, "null") + XCTAssertEqual(container.array, ["one", "", nil, "null", "~"]) + } } #endif