In my Swift app (ios 16/xcode 14), I call two different National Weather Service APIs and use JSONDecoder to decode the results.
if let decodedResponse = try? JSONDecoder().decode(AlertModel.self, from: data) {
if let decodedResponse = try? JSONDecoder().decode(ForecastModel.self, from: data) {
AlertModel:
//
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let alert = try? JSONDecoder().decode(Alert.self, from: jsonData)
import Foundation
// MARK: - Alert
struct AlertModel: Codable {
let context: [ContextElement]
let type: String
let features: [Feature]
let title: String
let updated: String
enum CodingKeys: String, CodingKey {
case context = "@context"
case type, features, title, updated
}
}
enum ContextElement: Codable {
case contextClass(ContextClass)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(ContextClass.self) {
self = .contextClass(x)
return
}
throw DecodingError.typeMismatch(ContextElement.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ContextElement"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .contextClass(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
// MARK: - ContextClass
struct ContextClass: Codable {
let version: String
let wx, vocab: String
enum CodingKeys: String, CodingKey {
case version = "@version"
case wx
case vocab = "@vocab"
}
}
// MARK: - Feature
struct Feature: Codable {
let id: String
let type: String
let geometry: JSONNull?
let properties: Properties
}
// MARK: - Properties
struct Properties: Codable {
let id: String
let type, propertiesID, areaDesc: String
let geocode: Geocode
let affectedZones: [String]
let references: [Reference]
let sent, effective, onset, expires: String
let ends: String
let status, messageType, category, severity: String
let certainty, urgency, event, sender: String
let senderName, headline, description, instruction: String
let response: String
let parameters: Parameters
enum CodingKeys: String, CodingKey {
case id = "@id"
case type = "@type"
case propertiesID = "id"
case areaDesc, geocode, affectedZones, references, sent, effective, onset, expires, ends, status, messageType, category, severity, certainty, urgency, event, sender, senderName, headline, description, instruction, response, parameters
}
}
// MARK: - Geocode
struct Geocode: Codable {
let same, ugc: [String]
enum CodingKeys: String, CodingKey {
case same = "SAME"
case ugc = "UGC"
}
}
// MARK: - Parameters
struct Parameters: Codable {
let awipSidentifier, wmOidentifier, nwSheadline, blockchannel: [String]
let vtec: [String]
let eventEndingTime: [String]
let easOrg, expiredReferences: [String]?
enum CodingKeys: String, CodingKey {
case awipSidentifier = "AWIPSidentifier"
case wmOidentifier = "WMOidentifier"
case nwSheadline = "NWSheadline"
case blockchannel = "BLOCKCHANNEL"
case vtec = "VTEC"
case eventEndingTime
case easOrg = "EAS-ORG"
case expiredReferences
}
}
// MARK: - Reference
struct Reference: Codable {
let id: String
let identifier, sender: String
let sent: String
enum CodingKeys: String, CodingKey {
case id = "@id"
case identifier, sender, sent
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
ForecastModel:
/ This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let forecastModel = try? JSONDecoder().decode(ForecastModel.self, from: jsonData)
import Foundation
// MARK: - ForecastModel
struct ForecastModel: Codable {
let context: [ContextElement]
let type: String
let geometry: Geometry
let properties: Properties
enum CodingKeys: String, CodingKey {
case context = "@context"
case type, geometry, properties
}
}
enum ContextElement: Codable {
case contextClass(ContextClass)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(ContextClass.self) {
self = .contextClass(x)
return
}
throw DecodingError.typeMismatch(ContextElement.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ContextElement"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .contextClass(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
//
// MARK: - ContextClass
struct ContextClass: Codable {
let version: String
let wx: String
let geo, unit: String
let vocab: String
enum CodingKeys: String, CodingKey {
case version = "@version"
case wx, geo, unit
case vocab = "@vocab"
}
}
// MARK: - Geometry
struct Geometry: Codable {
let type: String
let coordinates: [[[Double]]]
}
//// MARK: - Properties
struct Properties: Codable {
//let updated: Date
let updated: String
let units, forecastGenerator: String
//let generatedAt, updateTime: Date
let generatedAt, updateTime: String
let validTimes: String
let elevation: Elevation
let periods: [Period]
}
//// MARK: - Elevation
struct Elevation: Codable {
let unitCode: String
let value: Double
}
//// MARK: - Period
struct Period: Codable {
let number: Int
let name: String
//let startTime, endTime: Date
let startTime, endTime: String
let isDaytime: Bool
let temperature: Int
let temperatureUnit: TemperatureUnit
let temperatureTrend: JSONNull?
let windSpeed, windDirection: String
let icon: String
let shortForecast, detailedForecast: String
}
enum TemperatureUnit: String, Codable {
case f = "F"
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
As you can see there are name collisions between models. Both api's work fine with the other model commented out. How can I use some sort of name space trick to make these two models play nice together?
You could have ContextElement
added at the scope of the AlertModel
like
struct AlertModel: Codable {
struct ContextElement { ... }
...
}
This way your type will be e.g. AlertModel.ContextElement
. Read more about nested types.