Search code examples
jsonswiftstructweathernoaa

How to Decode the NOAA Weather JSON into a Swift Struct


When I request data from api.weather.gov I get the following JSON (extract, only up to the first period or hour:

{
"@context": [
"https://geojson.org/geojson-ld/geojson-context.jsonld",
{
"@version": "1.1",
"wx": "https://api.weather.gov/ontology#",
"geo": "http://www.opengis.net/ont/geosparql#",
"unit": "http://codes.wmo.int/common/unit/",
"@vocab": "https://api.weather.gov/ontology#"
}
],
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-95.406033800000003,
39.349170399999998
],
[
-95.40841660000001,
39.326447799999997
],
[
-95.382835100000008,
39.324328799999996
],
[
-95.380447500000002,
39.347050999999997
],
[
-95.406033800000003,
39.349170399999998
]
]
]
},
"properties": {
"units": "us",
"forecastGenerator": "HourlyForecastGenerator",
"generatedAt": "2024-07-07T13:28:43+00:00",
"updateTime": "2024-07-07T10:55:15+00:00",
"validTimes": "2024-07-07T04:00:00+00:00/P7DT21H",
"elevation": {
"unitCode": "wmoUnit:m",
"value": 9.1440000000000001
},
"periods": [
{
"number": 1,
"name": "",
"startTime": "2024-07-07T09:00:00-04:00",
"endTime": "2024-07-07T10:00:00-04:00",
"isDaytime": true,
"temperature": 82,
"temperatureUnit": "F",
"temperatureTrend": "",
"probabilityOfPrecipitation": {
"unitCode": "wmoUnit:percent",
"value": 15
},
"dewpoint": {
"unitCode": "wmoUnit:degC",
"value": 26.111111111111111
},
"relativeHumidity": {
"unitCode": "wmoUnit:percent",
"value": 89
},
"windSpeed": "5 mph",
"windDirection": "ESE",
"icon": "/icons/land/day/tsra_hi,20?size=small",
"shortForecast": "Isolated Showers And Thunderstorms",
"detailedForecast": ""
},
{
"number": 2,

I have been able to map it to a JSON Structure for the most part, with the exception of the first element:

"@context": [
"https://geojson.org/geojson-ld/geojson-context.jsonld",
{
"@version": "1.1",
"wx": "https://api.weather.gov/ontology#",
"geo": "http://www.opengis.net/ont/geosparql#",
"unit": "http://codes.wmo.int/common/unit/",
"@vocab": "https://api.weather.gov/ontology#"
}
],

This looks to me like a Swift Dictionary [String: [VersionStruct]], but in JSON looks like an Array with the first element a String and the second element a Struct of Strings, how do I represent this as a Swift Struct, I am confused, thank you


Solution

  • If the @context property always contains a list of two elements with the first being a string and the second being representable by a given type, you can use a tuple:

    struct GeoJSON {
      let context: (String, Context)
      ...
    
      struct Context {
        let version, wx, geo, unit, vocab: String
      }
    }
    

    Tuples are not Decodable so you need to provide a custom Decodable implementation:

    struct GeoJSON: Decodable {
      let context: (String, Context)
    
      enum CodingKeys: String, CodingKey {
        case context = "@context"
      }
    
      init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        var nested = try container.nestedUnkeyedContainer(forKey: .context)
        let string = try nested.decode(String.self)
        let context = try nested.decode(GeoJSON.Context.self)
        self.context = (string, context)
      }
    }
    
    extension GeoJSON {
      struct Context: Codable {
        let version, wx, geo, unit, vocab: String
    
        enum CodingKeys: String, CodingKey {
          case wx, geo, unit, version = "@version", vocab = "@vocab"
        }
      }
    }