New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to parse array of polymorphic objects? #8
Comments
Hi @rehsals...it absolutely is possible to use Elevate in this situation! ProblemSince you have different types inside an let properties = try Parser.parseProperties(data: data) { make in The problem here is that Elevate doesn't know about polymorphism from the JSON object. All it knows is that you want to construct instances of SolutionInstead, you can use a class PolymorphicModelObjectTestCase: XCTestCase {
func testThatElevateCanUseDecoderToSwitchBetweenPolymorphicModelObjects() {
do {
// Given
let json: AnyObject = [
[
"name": "cnoon"
],
[
"name": "rehsals",
"sport": "football"
]
]
let data = try NSJSONSerialization.dataWithJSONObject(json, options: .PrettyPrinted)
// When
let people: [Person] = try Parser.parseArray(data: data, withDecoder: PersonDecoder())
// Then
XCTAssertEqual(people.count, 2)
if people.count == 2 {
XCTAssertTrue(people[0] is Person)
XCTAssertTrue(people[1] is Teammate)
}
// Show me
people.forEach { print($0) }
} catch {
print("Error occurred: \(error)")
}
}
}
class Person: CustomStringConvertible {
let name: String
var description: String { return "Person: { \"name\": \"\(name)\" }" }
init(name: String) {
self.name = name
}
}
final class Teammate: Person {
let sport: String
override var description: String { return "Teammate: { \"name\": \"\(name)\", \"sport\": \"\(sport)\" }" }
init(name: String, sport: String) {
self.sport = sport
super.init(name: name)
}
}
class PersonDecoder: Decoder {
func decodeObject(object: AnyObject) throws -> Any {
let nameKeyPath = "name"
let sportKeyPath = "sport"
let properties = try Parser.parseProperties(json: object) { make in
make.propertyForKeyPath(nameKeyPath, type: .String)
make.propertyForKeyPath(sportKeyPath, type: .String, optional: true)
}
let name: String = properties <-! nameKeyPath
let sport: String? = properties <-? sportKeyPath
if let sport = sport {
return Teammate(name: name, sport: sport)
} else {
return Person(name: name)
}
}
} I'm fairly certain this example should match your use case exactly. @AtomicCat and I came up with a few different approaches, but this is probably the most elegant solution to this particular issue. Cheers. 🍻 |
This is eating at my brain... Alternative approach with fewer optionals: import Elevate
class A: Decodable {
let type: String
let thing: String
required init(json: AnyObject) throws {
let typePath = "type"
let thingPath = "thing"
let properties = try Parser.parseProperties(json: json) { make in
make.propertyForKeyPath(typePath, type: .String)
make.propertyForKeyPath(thingPath, type: .String)
}
self.type = properties <-! typePath
self.thing = properties <-! thingPath
}
}
final class B: A {
let other: String
required init(json: AnyObject) throws {
let otherPath = "other"
let properties = try Parser.parseProperties(json: json) { make in
make.propertyForKeyPath(otherPath, type: .String)
}
self.other = properties <-! otherPath
try super.init(json: json)
}
}
class PolyDecoder: Decoder {
func decodeObject(object: AnyObject) throws -> Any {
let typePath = "type"
let properties = try Parser.parseProperties(json: object) { make in
make.propertyForKeyPath(typePath, type: .String)
}
let type: String = properties <-! typePath
if type == "ClassA" {
return try A(json: object)
} else if type == "ClassB" {
return try B(json: object)
} else {
throw ParserError.Validation(failureReason: "Unknown type")
}
}
}
let json = [
[ "type": "ClassA", "thing": "foo" ],
[ "type": "ClassB", "thing": "bar", "other": "whatever" ],
]
do {
let data = try NSJSONSerialization.dataWithJSONObject(json, options: .PrettyPrinted)
let objects: [AnyObject] = try Parser.parseArray(data: data, withDecoder: PolyDecoder())
// Show me
objects.forEach { print($0) }
} catch {
print("Error occurred: \(error)")
} If you don't need B to be a subclass of A, one option would be to put the extra properties that B has in an optional struct in A and handle it in a single decoder. If they don't need to be classes, you might have additional options by using structs. |
Yep, totally a valid solution as well @AtomicCat. @rehsals take your pick. Depending on your actual use case, one may make more sense than the other. Cheers. 🍻 |
Great! Thanks for your help, @cnoon. And thanks to @AtomicCat for another approach. |
Hi! Thanks for wonderful framework.
I've encountered one issue, I can't figure out.
Let's say I have:
and
In my response I have JSON-array of objects and some of them turn out to be
B
's. The type of object determined bytype
field. Is it possible to use Elevate in this situation? How should I organise my decoding?The text was updated successfully, but these errors were encountered: