Lets say i have this json from an API request:
friends: {
"john":31,
"mark":27,
"lisa":17,
"tom":41
}
I usually expect it in an array format:
friends: [
{ "john":31 },
{ "mark":27 },
{ "lisa":17 },
{ "tom":41 }
]
But the API doesn't provide me this way the results. So i want finally to map it to an array of [Friend], where Friend is:
class Friend: Decodable {
let name: String
let age: Int
}
How should i serialize this json to get [Friend] ?
First of all, example isn't valid json at all. To be valid it either shouldn't include "friends" label, or it should be embedded in another object like this
{
"friends": {
"john":31,
"mark":27,
"lisa":17,
"tom":41
}
}
If I understand question correctly, you want to decode json object to swift array. I don't think there is a way to do so without writing custom decoding. Instead, you can decode json into Dictionary and when manually map it like so
struct Friend {
let name: String
let age: Int
}
struct Friends: Decodable {
let friends: [String: Int]
}
let friends = try! JSONDecoder().decode(Friends.self, from: json.data(using: .utf8)!)
.friends
.map { (name, age) in Friend(name: name, age: age) }
Disclaimer: I recommend to change you API format to one like in #scriptable's answer (which was deleted while I was answering, hm), where name and age fields are properly defined. And where you're not limited to basically a pair of key-value to parse.
But if you can't or won't change it you may use something like this to decode your Friend type:
struct Friend: Decodable {
let name: String
let age: UInt
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dictionary = try container.decode([String: UInt].self)
guard dictionary.count == 1, let (name, age) = dictionary.first else {
throw DecodingError.invalidFriendDictionary
}
self.name = name
self.age = age
}
enum DecodingError: Error {
case invalidFriendDictionary
}
}
struct Friends: Decodable {
let friends: [Friend]
}
let friends = try JSONDecoder().decode(Friends.self, from: data)
It assumes key is name and value is age. And checks that there's only a one pair to parse.
Related
I'm trying to parse a json that looks like this using decodable:
{
"count": 1,
"results": [
{
"title": 1,
"content": "Bla"
} ]
}
My problem is that I don't want to make a class that has a count property just to be able to use the decoder. I want to parse only the results part I don't care about the count.
So my question is, can decodable.decode somehow only parse a part of the result json. I mean a certain key path instead of the whole json ? And I want to do it using Decodable.
In a nutshell I don't want this:
class IncidentWrapper: Codable{
var count: Int
var incident: [Incident]
}
What I would Imagine is to have this:
decodable.decode([Incident].self, from: response.data, forKey: "results")
Thanks
let me see what I can suggest:
struct Result: Codeable {
var id: Int
var message: String
var color: String
var type: String
enum CodingKeys: String, CodingKey {
case results
}
enum NestedResultKeys: String, CodingKey {
case id, message, color, type
}
}
extension Result: Decodable {
init(from decoder: Decoder) throws {
let result = try decoder.container(keyedBy: CodingKeys.self)
let nestedResult = try result.nestedContainer(keyedBy: NestedResultKeys.self, forKey: .result)
id = try nestedResult.decode(Int.self, forKey: .id)
message = try nestedResult.decode(String.self, forKey: .message)
color = try nestedResult.decode(String.self, forKey: .color)
type = try nestedResult.decode(String.self, forKey: .id)
}
}
See this documentation for more insight
https://developer.apple.com/documentation/swift/swift_standard_library/encoding_decoding_and_serialization
Hope it helps your project!
You probably is looking for JSONSerialization class. This is an example how it works:
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
if let results = json["results"] as? [Incident] {
print(results.count)
}
}
You can define a generic wrapper for once and use everywhere.
It will work as a generic wrapper for results key only.
protocol ResultsDecodable: Decodable{
associatedtype T: Decodable
var results: [T] {get}
}
struct Result<Element: Decodable>: ResultsDecodable{
typealias T = Element
var results: [Element]
}
Extend JSONDecoder to get results output.
extension JSONDecoder {
func resultDecode<T>(_ type: Result<T>.Type, from data: Data) throws -> [T] where T : Decodable{
let model = try! decode(type, from: data)
return model.results
}
}
And use like this
var str = #"{"count": 1,"results": [{"title": 1,"content": "Bla"}, {"title": 2,"content": "Bla"} ]}"#
class Incident: Decodable{
let title: Int
let content: String
}
let indicents = (try! JSONDecoder().resultDecode(Result<Incident>.self, from: str.data(using: .utf8)!))
See how it makes everything more complex. BETTER USE IncidentWrapper!!!
You only need to use the keys you care about. Just leave off the count. Don't make it part of your struct.
You will only get errors if you can't find a key in the json that you are expecting in the struct. You can avoid this too, though, if you make it an optional in the struct.
I have this json:
{ "stuff": [
{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66 }},
{
"type":"house",
"object":{
"d":66,
"e":66,
"f":66 }},
{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66 }}
]}
As you can see for "car" and "house" there are different "object" structs, but both under the tag "object".
It would be ideal if one ended up with something like
struct StuffItem: Decodable {
let type: TheType
let car: Car
let house: House
}
Is there some Codable, swifty, way to handle this?
The swiftiest way in my opinion is an enum with associated types
This is valid JSON
let jsonString = """
{ "stuff": [
{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66
}
},{
"type":"house",
"object":{
"d":66,
"e":66,
"f":66
}
},{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66
}
}
]}
"""
These are the structs
struct Root : Decodable {
let stuff : [Object]
}
enum Type : String, Decodable { case car, house }
struct Car : Decodable {
let a, b, c : Int
}
struct House : Decodable {
let d, e, f : Int
}
enum Object : Decodable {
case house(House), car(Car)
private enum CodingKeys : String, CodingKey { case type, object }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(Type.self, forKey: .type)
switch type {
case .car:
let carData = try container.decode(Car.self, forKey: .object)
self = .car(carData)
case .house:
let houseData = try container.decode(House.self, forKey: .object)
self = .house(houseData)
}
}
}
And the code to decode the JSON
do {
let result = try JSONDecoder().decode(Root.self, from: Data(jsonString.utf8))
let objects = result.stuff
for object in objects {
switch object {
case .car(let car): print(car)
case .house(let house): print(house)
}
}
} catch {
print(error)
}
You can handle multiple cases by using the enum just define your type, Giving code will help you to parse the JSON by using Struct modal with enum.
// MARK: - Welcome
struct Welcome: Codable {
let stuff: [Stuff]
}
// MARK: - Stuff
struct Stuff: Codable {
let type: String
let object: Object
}
// MARK: - Object
struct Object: Codable {
let a, b, c, d: Int?
let e, f: Int?
}
enum Type: String {
case car
case house
}
func fetchResponse() {
do {
let jsonString = "your json string"
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Welcome.self, from: data)
let objects = result.stuff
let carObjects = objects.filter{$0.type == Type.car.rawValue}
print("Its car array: \(carObjects)")// if you need filters car object then use this
let houseObjects = objects.filter{$0.type == Type.house.rawValue}// if you need filters house object then use this
print("Its house array: \(houseObjects)")
// or you check in loop also
objects.forEach { (stuff) in
switch stuff.type {
case Type.car.rawValue:
print("Its car object")
case Type.house.rawValue:
print("Its house object")
default:
print("Also you can set your one case in `default`")
break
}
}
} catch {
print(error.localizedDescription)
}
}
Another explantion:
Since there are not many explanations of this around, here's another example of what #vadian has explained:
1. In Swift, use enum instead of struct to achieve 'flexible type'.
2. You then need parsers for the 'item', and for the 'type'.
2. Parse, and then just switch to decode, and set the correct type.
So for the JSON above, you'd have
struct YourFeed: Decodable {
let stuff: [Item]
}
Each item can be a Car or a House.
struct Car: Decodable { ... }
struct House: Decodable { ... }
So those are easy.
Now for Item. It can be more than one type.
In Swift, use enum instead of struct to achieve 'flexible type'.
// in Swift, an "enum" is basically a "struct" which can have a flexible type,
// so here we have enum Item rather than struct Item:
enum Item: Decodable {
// so this thing, Item, can be one of these two types:
case car(Car)
case house(House)
Next, simply mirror that in a raw enum which will be used for parsing the "type" field. (You can call it anything, I've just called it "Parse".)
// the relevant key strings for parsing the "type" field:
private enum Parse: String, Decodable {
case car
case house
}
Next, look at the original JSON up top. Each "item" has two fields, "type" and "object". Here they are in a raw enum. (Again you can call it anything, I've just called it "Keys" here.)
// we're decoding an item, what are the top-level tags in item?
private enum Keys: String, CodingKey {
// so, these are just the two fields in item from the json
case type
case object
}
Have an enum to parse the 'item' level, and an enum to parse the 'type'.
Finally, write the initializer for "Item". Simply decode the both the top level and the "type" ...
init(from decoder: Decoder) throws {
// parse the top level
let c = try decoder.container(keyedBy: Keys.self)
// and parse the 'type' field
let t = try c.decode(Parse.self, forKey: .type)
... and you're done. Decode the data (using the relevant class), and set the "Item" enum object to the appropriate type.
Parse those, and then just switch to decode / set the enum.
// we're done, so depending on which of the types it is,
// decode (using the relevant decoder), and become the relevant type:
switch t {
case .car:
let d = try c.decode(Car.self, forKey: .object)
self = .car(d)
case .house:
let d = try c.decode(House.self, forKey: .object)
self = .house(d)
}
}
}
Here's the whole thing in one go:
enum Item: Decodable {
case car(Car)
case house(House)
// the relevant key strings for parsing the 'type' field:
private enum Parse: String, Decodable {
case car
case house
}
// the top-level tags in 'item':
private enum Keys: String, CodingKey {
case type
case object
}
init(from decoder: Decoder) throws {
// parse the top level
let c = try decoder.container(keyedBy: Keys.self)
// parse the 'type' field
let t = try c.decode(Parse.self, forKey: .type)
// we're done, switch to
// decode (using the relevant decoder), and become the relevant type:
switch t {
case .car:
let d = try c.decode(Car.self, forKey: .object)
self = .car(d)
case .house:
let d = try c.decode(House.self, forKey: .object)
self = .house(d)
}
}
}
I am trying to validated students == null or values avilable, If values avilable I need to get grade and store grade into table data array and subject null also I need to store in same array For example: [10, null, 11] from below JSON. how to append like this in single array from JSON response.
{
"students":[
{
"id":0,
"subject":[
{
"grade":10
}
]
},
{
"id":1,
"subject":null
},
{
"id":2,
"subject":[
{
"grade":11
}
]
}
]
}
Expected output: [10,null,11,......] //This array I am going to use Tableview cell
I am validating based on null and not null array values within cell for row. I can use var array = [String?] for accepting null values but how to append two different field result into same array?
You should take a look into the 'Codable' protocol.
By simply defining a struct like:
struct Student: Codable
you can decode it from JSON into these objects.
See for example: hackernoon or grokswift
This looks like a trivial scenario. Best solution is Decodable. Your payload loaded from network or whatever will be parsed into structure. Now you can easily make any manipulations.
Setup: Open a new project. Add "payload.json" file with json payload you provided in question.
Add the following to your project.
import UIKit
struct StudentData: Decodable {
var students: [Student]
}
struct Student: Decodable {
var id: Int
var subject: [Subject]?
}
struct Subject: Decodable {
var grade: Int
}
class ViewController: UIViewController {
var data: Data? {
guard let path = Bundle(for: type(of: self)).path(forResource: "payload", ofType: "json") else { return nil }
return try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
}
override func viewDidLoad() {
super.viewDidLoad()
if let data = data {
do {
let studentData = try JSONDecoder().decode(StudentData.self, from: data)
print(studentData)
// manipulate the structure in any way you want
let subjects: [Subject?] = studentData.students.map { $0.subject?.first }
print(subjects)
let nonNilValues = subjects.compactMap { $0 }
print(subjects)
// ... etc
} catch let error {
print(error.localizedDescription)
}
}
}
}
Sorry for not coding in playgrounds. It's way too buggy.
Try this
let students = [["id": 0,"subject": [["grade": 10]]],
["id": 0,"subject": nil],
["id": 0,"subject": [["grade": 10]]]] as! [Dictionary<String,Any>]
let array = students.map({(($0["subject"] as? [Any])?.first as? Dictionary<String,Int>)?["grade"]})
print(array)
I have the following code to extract a JSON contained within a coding key:
let value = try! decoder.decode([String:Applmusic].self, from: $0["applmusic"])
This successfully handles the following JSONs:
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry",
}
However, fails to extract a JSON with the coding key of applmusic from the following one:
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry",
},
"spotify":{
"differentcode":"SPOT",
"music_quality":"good",
"spotify_specific_code":"absent in apple"
},
"amazon":{
"amzncode":"SPOT",
"music_quality":"good",
"stanley":"absent in apple"
}
}
The data models for applmusic,spotify and amazon are different. However, I need only to extract applmusic and omit other coding keys.
My Swift data model is the following:
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
The API responds with the full JSON and I cannot ask it to give me only the needed fields.
How to decode only the specific part of the json? It seems, that Decodable requires me to deserialize the whole json first, so I have to know the full data model for it.
Obviously, one of the solutions would be to create a separate Response model just to contain the applmusicparameter, but it looks like a hack:
public struct Response: Codable {
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
// The only parameter is `applmusic`, ignoring the other parts - works fine
public let applmusic: Applmusic
}
Could you propose a better way to deal with such JSON structures?
A little bit more insight
I use it the following technique in the generic extension that automatically decodes the API responses for me. Therefore, I'd prefer to generalize a way for handling such cases, without the need to create a Root structure. What if the key I need is 3 layers deep in the JSON structure?
Here is the extension that does the decoding for me:
extension Endpoint where Response: Swift.Decodable {
convenience init(method: Method = .get,
path: Path,
codingKey: String? = nil,
parameters: Parameters? = nil) {
self.init(method: method, path: path, parameters: parameters, codingKey: codingKey) {
if let key = codingKey {
guard let value = try decoder.decode([String:Response].self, from: $0)[key] else {
throw RestClientError.valueNotFound(codingKey: key)
}
return value
}
return try decoder.decode(Response.self, from: $0)
}
}
}
The API is defined like this:
extension API {
static func getMusic() -> Endpoint<[Applmusic]> {
return Endpoint(method: .get,
path: "/api/music",
codingKey: "applmusic")
}
}
Updated: I made an extension of JSONDecoder out of this answer, you can check it here: https://github.com/aunnnn/NestedDecodable, it allows you to decode a nested model of any depth with a key path.
You can use it like this:
let post = try decoder.decode(Post.self, from: data, keyPath: "nested.post")
You can make a Decodable wrapper (e.g., ModelResponse here), and put all the logic to extract nested model with a key inside that:
struct DecodingHelper {
/// Dynamic key
private struct Key: CodingKey {
let stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
let intValue: Int?
init?(intValue: Int) {
return nil
}
}
/// Dummy model that handles model extracting logic from a key
private struct ModelResponse<NestedModel: Decodable>: Decodable {
let nested: NestedModel
public init(from decoder: Decoder) throws {
let key = Key(stringValue: decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String)!
let values = try decoder.container(keyedBy: Key.self)
nested = try values.decode(NestedModel.self, forKey: key)
}
}
static func decode<T: Decodable>(modelType: T.Type, fromKey key: String) throws -> T {
// mock data, replace with network response
let path = Bundle.main.path(forResource: "test", ofType: "json")!
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let decoder = JSONDecoder()
// ***Pass in our key through `userInfo`
decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!] = key
let model = try decoder.decode(ModelResponse<T>.self, from: data).nested
return model
}
}
You can pass your desired key through userInfo of JSONDecoder ("my_model_key"). It is then converted to our dynamic Key inside ModelResponse to actually extract the model.
Then you can use it like this:
let appl = try DecodingHelper.decode(modelType: Applmusic.self, fromKey: "applmusic")
let amazon = try DecodingHelper.decode(modelType: Amazon.self, fromKey: "amazon")
let spotify = try DecodingHelper.decode(modelType: Spotify.self, fromKey: "spotify")
print(appl, amazon, spotify)
Full code:
https://gist.github.com/aunnnn/2d6bb20b9dfab41189a2411247d04904
Bonus: Deeply nested key
After playing around more, I found you can easily decode a key of arbitrary depth with this modified ModelResponse:
private struct ModelResponse<NestedModel: Decodable>: Decodable {
let nested: NestedModel
public init(from decoder: Decoder) throws {
// Split nested paths with '.'
var keyPaths = (decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String).split(separator: ".")
// Get last key to extract in the end
let lastKey = String(keyPaths.popLast()!)
// Loop getting container until reach final one
var targetContainer = try decoder.container(keyedBy: Key.self)
for k in keyPaths {
let key = Key(stringValue: String(k))!
targetContainer = try targetContainer.nestedContainer(keyedBy: Key.self, forKey: key)
}
nested = try targetContainer.decode(NestedModel.self, forKey: Key(stringValue: lastKey)!)
}
Then you can use it like this:
let deeplyNestedModel = try DecodingHelper.decode(modelType: Amazon.self, fromKey: "nest1.nest2.nest3")
From this json:
{
"apple": { ... },
"amazon": {
"amzncode": "SPOT",
"music_quality": "good",
"stanley": "absent in apple"
},
"nest1": {
"nest2": {
"amzncode": "Nest works",
"music_quality": "Great",
"stanley": "Oh yes",
"nest3": {
"amzncode": "Nest works, again!!!",
"music_quality": "Great",
"stanley": "Oh yes"
}
}
}
}
Full code: https://gist.github.com/aunnnn/9a6b4608ae49fe1594dbcabd9e607834
You don't really need the nested struct Applmusic inside Response. This will do the job:
import Foundation
let json = """
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry"
},
"I don't want this":"potatoe",
}
"""
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
public struct Response: Codable {
public let applmusic: Applmusic
}
if let data = json.data(using: .utf8) {
let value = try! JSONDecoder().decode(Response.self, from: data).applmusic
print(value) // Applmusic(code: "AAPL", quality: "good", line: "She told me don\'t worry")
}
Edit: Addressing your latest comment
If the JSON response would change in a way that the applmusic tag is nested, you would only need to properly change your Response type. Example:
New JSON (note that applmusic is now nested in a new responseData tag):
{
"responseData":{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry"
},
"I don't want this":"potatoe",
}
}
The only change needed would be in Response:
public struct Response: Decodable {
public let applmusic: Applmusic
enum CodingKeys: String, CodingKey {
case responseData
}
enum ApplmusicKey: String, CodingKey {
case applmusic
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let applmusicKey = try values.nestedContainer(keyedBy: ApplmusicKey.self, forKey: .responseData)
applmusic = try applmusicKey.decode(Applmusic.self, forKey: .applmusic)
}
}
The previous changes wouldn't break up any existing code, we are only fine-tuning the private implementation of how the Response parses the JSON data to correctly fetch an Applmusic object. All calls such as JSONDecoder().decode(Response.self, from: data).applmusic would remain the same.
Tip
Finally, if you want to hide the Response wrapper logic altogether, you may have one public/exposed method which will do all the work; such as:
// (fine-tune this method to your needs)
func decodeAppleMusic(data: Data) throws -> Applmusic {
return try JSONDecoder().decode(Response.self, from: data).applmusic
}
Hiding the fact that Response even exists (make it private/inaccessible), will allow you to have all the code through your app only have to call decodeAppleMusic(data:). For example:
if let data = json.data(using: .utf8) {
let value = try! decodeAppleMusic(data: data)
print(value) // Applmusic(code: "AAPL", quality: "good", line: "She told me don\'t worry")
}
Recommended read:
Encoding and Decoding Custom Types
https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
Interesting question. I know that it was 2 weeks ago but I was wondering
how it can be solved using library KeyedCodable I created. Here is my proposition with generic:
struct Response<Type>: Codable, Keyedable where Type: Codable {
var responseObject: Type!
mutating func map(map: KeyMap) throws {
try responseObject <-> map[map.userInfo.keyPath]
}
init(from decoder: Decoder) throws {
try KeyedDecoder(with: decoder).decode(to: &self)
}
}
helper extension:
private let infoKey = CodingUserInfoKey(rawValue: "keyPath")!
extension Dictionary where Key == CodingUserInfoKey, Value == Any {
var keyPath: String {
set { self[infoKey] = newValue }
get {
guard let key = self[infoKey] as? String else { return "" }
return key
}
}
use:
let decoder = JSONDecoder()
decoder.userInfo.keyPath = "applmusic"
let response = try? decoder.decode(Response<Applmusic>.self, from: jsonData)
Please notice that keyPath may be nested more deeply I mean it may be eg. "responseData.services.applemusic".
In addition Response is a Codable so you can encode it without any additional work.
I've got some JSON messages coming in over a websocket connection.
// sample message
{
type: "person",
data: {
name: "john"
}
}
// some other message
{
type: "location",
data: {
x: 101,
y: 56
}
}
How can I convert those messages into proper structs using Swift 4 and the Codable protocol?
In Go I can do something like: "Hey at the moment I only care about the type field and I'm not interested in the rest (the data part)." It would look like this
type Message struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
As you can see Data is of type json.RawMessage which can be parsed later on. Here is a full example https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal.
Can I do something similar in Swift? Like (haven't tried it yet)
struct Message: Codable {
var type: String
var data: [String: Any]
}
Then switch on the type to convert the dictionary into proper structs. Would that work?
I wouldn't rely upon a Dictionary. I'd use custom types.
For example, let's assume that:
you know which object you're going to get back (because of the nature of the request); and
the two types of response truly return identical structures except the contents of the data.
In that case, you might use a very simple generic pattern:
struct Person: Decodable {
let name: String
}
struct Location: Decodable {
let x: Int
let y: Int
}
struct ServerResponse<T: Decodable>: Decodable {
let type: String
let data: T
}
And then, when you want to parse a response with a Person, it would be:
let data = json.data(using: .utf8)!
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Person>.self, from: data)
let person = responseObject.data
print(person)
} catch let parseError {
print(parseError)
}
Or to parse a Location:
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Location>.self, from: data)
let location = responseObject.data
print(location)
} catch let parseError {
print(parseError)
}
There are more complicated patterns one could entertain (e.g. dynamic parsing of the data type based upon the type value it encountered), but I wouldn't be inclined to pursue such patterns unless necessary. This is a nice, simple approach that accomplishes typical pattern where you know the associated response type for a particular request.
If you wanted you could validate the type value with what was parsed from the data value. Consider:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse<T: Payload>: Decodable {
let type: PayloadType
let data: T
}
Then, your parse function could not only parse the right data structure, but confirm the type value, e.g.:
enum ParseError: Error {
case wrongPayloadType
}
func parse<T: Payload>(_ data: Data) throws -> T {
let responseObject = try JSONDecoder().decode(ServerResponse<T>.self, from: data)
guard responseObject.type == T.payloadType else {
throw ParseError.wrongPayloadType
}
return responseObject.data
}
And then you could call it like so:
do {
let location: Location = try parse(data)
print(location)
} catch let parseError {
print(parseError)
}
That not only returns the Location object, but also validates the value for type in the server response. I'm not sure it's worth the effort, but in case you wanted to do so, that's an approach.
If you really don't know the type when processing the JSON, then you just need to write an init(coder:) that first parses the type, and then parses the data depending upon the value that type contained:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse: Decodable {
let type: PayloadType
let data: Payload
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(PayloadType.self, forKey: .type)
switch type {
case .person:
data = try values.decode(Person.self, forKey: .data)
case .location:
data = try values.decode(Location.self, forKey: .data)
}
}
enum CodingKeys: String, CodingKey {
case type, data
}
}
And then you can do things like:
do {
let responseObject = try JSONDecoder().decode(ServerResponse.self, from: data)
let payload = responseObject.data
if payload is Location {
print("location:", payload)
} else if payload is Person {
print("person:", payload)
}
} catch let parseError {
print(parseError)
}