Search code examples
swiftmodelnamespaces

How to use two different Struct Models with name collisions


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?


Solution

  • 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.