Skip to content

Commit

Permalink
for subclasses: decode the discriminator first, and only try decoding…
Browse files Browse the repository at this point in the history
… the sub-object for the matching discriminator value
  • Loading branch information
gereons committed Oct 11, 2024
1 parent 07bebbb commit 5c99ec0
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 22 deletions.
20 changes: 13 additions & 7 deletions Sources/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,15 @@ final class Generator {
let discriminatorType = schema.properties?[discriminator.propertyName]

let discriminatorIsString: Bool
let discriminatorTypeName: String
switch discriminatorType {
case .ref:
case .ref(let ref):
discriminatorIsString = false
discriminatorTypeName = ref.swiftType().name
case .property(let prop):
assert(prop.type == "string", "\(modelName): unexpected discriminator type")
discriminatorIsString = true
discriminatorTypeName = "String"
case .none:
fatalError("\(modelName): unknown discriminator type")
}
Expand Down Expand Up @@ -322,22 +325,25 @@ final class Generator {
}
print("")

block("enum DiscriminatorKeys: String, CodingKey") {
print(#"case type = "\#(discriminator.propertyName)""#)
}
print("")

// init method
block("public init(from decoder: Decoder) throws") {
print("let container = try decoder.container(keyedBy: DiscriminatorKeys.self)")
print("let type = try container.decode(\(discriminatorTypeName).self, forKey: .type)")
print("")
for dc in discriminatorCases {
let compare = discriminatorIsString ? #""\#(dc.rawString)""# : ".\(dc.enumCase)"
print(#"if let obj = try? \#(dc.mappedModel)(from: decoder), obj.\#(discriminator.propertyName) == \#(compare) {"#)
print("if type == \(compare), let obj = try? \(dc.mappedModel)(from: decoder) {")
indent {
print("self = .\(dc.enumCase)(obj)")
}
print("} else ", terminator: "")
}
block {
block("enum DiscriminatorKeys: String, CodingKey") {
print(#"case type = "\#(discriminator.propertyName)""#)
}
print("let container = try decoder.container(keyedBy: DiscriminatorKeys.self)")
print("let type = try container.decode(String.self, forKey: .type)")
print(#"throw DecodingError.typeMismatch(\#(modelName).self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "unexpected subclass type \(type)"))"#)
}
}
Expand Down
16 changes: 9 additions & 7 deletions Tests/InheritanceTest1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,19 @@ final class InheritanceTest1: XCTestCase {
case cat(Cat)
case dog(Dog)
enum DiscriminatorKeys: String, CodingKey {
case type = "status"
}
public init(from decoder: Decoder) throws {
if let obj = try? Cat(from: decoder), obj.status == .cat {
let container = try decoder.container(keyedBy: DiscriminatorKeys.self)
let type = try container.decode(AnimalType.self, forKey: .type)
if type == .cat, let obj = try? Cat(from: decoder) {
self = .cat(obj)
} else if let obj = try? Dog(from: decoder), obj.status == .dog {
} else if type == .dog, let obj = try? Dog(from: decoder) {
self = .dog(obj)
} else {
enum DiscriminatorKeys: String, CodingKey {
case type = "status"
}
let container = try decoder.container(keyedBy: DiscriminatorKeys.self)
let type = try container.decode(String.self, forKey: .type)
throw DecodingError.typeMismatch(Animal.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "unexpected subclass type \(type)"))
}
}
Expand Down
18 changes: 10 additions & 8 deletions Tests/InheritanceTest2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,21 @@ final class InheritanceTest2: XCTestCase {
case dog(Dog)
case animal(AnimalBase)
enum DiscriminatorKeys: String, CodingKey {
case type = "status"
}
public init(from decoder: Decoder) throws {
if let obj = try? Cat(from: decoder), obj.status == "cat" {
let container = try decoder.container(keyedBy: DiscriminatorKeys.self)
let type = try container.decode(String.self, forKey: .type)
if type == "cat", let obj = try? Cat(from: decoder) {
self = .cat(obj)
} else if let obj = try? Dog(from: decoder), obj.status == "dog" {
} else if type == "dog", let obj = try? Dog(from: decoder) {
self = .dog(obj)
} else if let obj = try? AnimalBase(from: decoder), obj.status == "Animal" {
} else if type == "Animal", let obj = try? AnimalBase(from: decoder) {
self = .animal(obj)
} else {
enum DiscriminatorKeys: String, CodingKey {
case type = "status"
}
let container = try decoder.container(keyedBy: DiscriminatorKeys.self)
let type = try container.decode(String.self, forKey: .type)
throw DecodingError.typeMismatch(Animal.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "unexpected subclass type \(type)"))
}
}
Expand Down

0 comments on commit 5c99ec0

Please sign in to comment.