From fc78958f76f7c12e060cb8a6d5620a834e9c3151 Mon Sep 17 00:00:00 2001 From: Janko Luin Date: Tue, 2 Jul 2019 16:49:47 +0200 Subject: [PATCH] Allow omitting `relationships` if all are optional When all relationships are optional, the `relationships` key is also optional and not required in the structure. I'm not super happy with importing Foundation and creating new objects any time a key is missing, but ultimately none of my attempts at conditional generics worked out for me. --- Sources/JSONAPI/Resource/ResourceObject.swift | 7 ++-- Tests/JSONAPITests/Entity/EntityTests.swift | 32 +++++++++++++++++++ .../Entity/stubs/EntityStubs.swift | 10 ++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Sources/JSONAPI/Resource/ResourceObject.swift b/Sources/JSONAPI/Resource/ResourceObject.swift index cdbdd7e..e53c1f4 100644 --- a/Sources/JSONAPI/Resource/ResourceObject.swift +++ b/Sources/JSONAPI/Resource/ResourceObject.swift @@ -5,6 +5,8 @@ // Created by Mathew Polzin on 7/24/18. // +import Foundation + /// A JSON API structure within an ResourceObject that contains /// named properties of types `ToOneRelationship` and /// `ToManyRelationship`. @@ -582,7 +584,6 @@ public extension ResourceObject { } init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ResourceObjectCodingKeys.self) let type = try container.decode(String.self, forKey: .type) @@ -597,7 +598,9 @@ public extension ResourceObject { attributes = try (NoAttributes() as? Description.Attributes) ?? container.decode(Description.Attributes.self, forKey: .attributes) - relationships = try (NoRelationships() as? Description.Relationships) ?? container.decode(Description.Relationships.self, forKey: .relationships) + relationships = try (NoRelationships() as? Description.Relationships) + ?? container.decodeIfPresent(Description.Relationships.self, forKey: .relationships) + ?? JSONDecoder().decode(Description.Relationships.self, from: "{}".data(using: .utf8)!) meta = try (NoMetadata() as? MetaType) ?? container.decode(MetaType.self, forKey: .meta) diff --git a/Tests/JSONAPITests/Entity/EntityTests.swift b/Tests/JSONAPITests/Entity/EntityTests.swift index 18e313e..ae44904 100644 --- a/Tests/JSONAPITests/Entity/EntityTests.swift +++ b/Tests/JSONAPITests/Entity/EntityTests.swift @@ -403,6 +403,16 @@ extension EntityTests { data: entity_optional_nullable_nulled_relationship) } + func test_optionalNullableRelationshipOmitted() { + let entity = decoded(type: TestEntity12.self, + data: entity_all_relationships_optional_and_omitted) + + XCTAssertNil(entity ~> \.optionalOne) + XCTAssertNil(entity ~> \.optionalNullableOne) + XCTAssertNil(entity ~> \.optionalMany) + XCTAssertNoThrow(try TestEntity12.check(entity)) + } + func test_nullableRelationshipIsNull() { let entity = decoded(type: TestEntity9.self, data: entity_nulled_relationship) @@ -806,6 +816,28 @@ extension EntityTests { typealias TestEntity11 = BasicEntity + enum TestEntityType12: ResourceObjectDescription { + public static var jsonType: String { return "twelfth_test_entities" } + + typealias Attributes = NoAttributes + + public struct Relationships: JSONAPI.Relationships { + public init() { + optionalOne = nil + optionalNullableOne = nil + optionalMany = nil + } + + let optionalOne: ToOneRelationship? + + let optionalNullableOne: ToOneRelationship? + + let optionalMany: ToManyRelationship? + } + } + + typealias TestEntity12 = BasicEntity + enum UnidentifiedTestEntityType: ResourceObjectDescription { public static var jsonType: String { return "unidentified_test_entities" } diff --git a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift index 1384c11..58343ce 100644 --- a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift +++ b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift @@ -383,6 +383,16 @@ let entity_valid_validated_attribute = """ } """.data(using: .utf8)! +let entity_all_relationships_optional_and_omitted = """ +{ + "id": "1", + "type": "twelfth_test_entities", + "attributes": { + "number": 10 + } +} +""".data(using: .utf8)! + let entity_unidentified = """ { "type": "unidentified_test_entities",