aman
aman

Reputation: 539

swift 4 firebase GeoPoint decode

I was wondering if it is possible to encode and decode a GeoPoint from the firebase's JSON response using standard swift 4?

It looks like as of now that the GeoPoint is not Codable?

I get the following error

No 'decode' candidates produce the expected contextual result type 'GeoPoint'

in my Codable class

final class Data: Codable
{
  var location:GeoPoint = GeoPoint(latitude:0,longitude:0)

  private enum CodingKeys: String, CodingKey
  {
    case geoloc
  }

  init(from decoder: Decoder) throws
  {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    do
    {
      located = try values.decode(Location.self, forKey: .geoloc) //error here
    }
    catch
    {
      print("data has location in server response\n")
    }

  }

  func encode(to encoder: Encoder) throws
  {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(location, forKey: .geoloc)

  }
}

Upvotes: 1

Views: 3008

Answers (4)

Ben Kax
Ben Kax

Reputation: 466

I just use the underlying keys, then init GeoPoint with coordinate

struct CustomGeoPoint : Codable {

    enum CodingKeys: String, CodingKey {
        case latitude 
        case longitude
    }

    var latitude: Double
    var longitude: Double
}

Upvotes: 1

YanSte
YanSte

Reputation: 10839

You can if you want use that

public typealias GeoPoint = CLLocationCoordinate2D

and add extension to Codable to CLLocationCoordinate2D or GeoPoint is the same at this point

Upvotes: 0

Patru
Patru

Reputation: 4551

Following the suggestion in the answer to this question you could greatly simplify your code by extending Geopoint in an extension. This is even possible if Geopoint was a struct as can be seen in the following Playground:

import Cocoa

struct Geopoint {
    let longitude: Double
    let latitude: Double
}

extension Geopoint : Codable {
    enum CodingKeys: String, CodingKey {
        case longitude, latitude
    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
    }
}

let jsonData = """
{
    "longitude": 14.334,
    "latitude": 41.342
}
""".data(using: .utf8)!
do {
    let point = try JSONDecoder().decode(Geopoint.self, from:jsonData)
    print(point)
} catch {
    print(error)
}

Not quite as painless as implementing Codable in a plain struct, but still a lot less painful than what you proposed.

Upvotes: 1

aman
aman

Reputation: 539

I was able to extend the GeoPoint class and make it Codable. That is how I solved it.

import UIKit

final class MyGeoPoint: GeoPoint, Codable
{
  override init(latitude: Double, longitude: Double)
  {
    super.init(latitude: latitude, longitude: longitude)
  }

  private enum CodingKeys: String, CodingKey
  {
    case latitude  = "_latitude"
    case longitude = "_longitude"
  }

  init(from decoder: Decoder) throws
  {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    var lat:Double   = 0
    var lon:Double  = 0

    do
    {
      lat = try container.decode(Double.self, forKey: .latitude)
    }
    catch
    {
      print("no latitude for MyGeoPoint")
    }

    do
    {
      lon = try container.decode(Double.self, forKey: .longitude)
    }
    catch
    {
      print("no longitude for MyGeoPoint")
    }


    super.init(latitude: lat, longitude: lon)
  }

  func encode(to encoder: Encoder) throws
  {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(latitude,  forKey: .latitude)
    try container.encode(longitude, forKey: .longitude)
  }

}

Now I can use my original Data class to consume the JSON response from Google, using my extended MyGeoPoint class (instead of Google's GeoPoint directly)

final class Data: Codable
{
  var location:MyGeoPoint = MyGeoPoint(latitude:0,longitude:0)
  private enum CodingKeys: String, CodingKey
  {
    case geoloc
  }
  init(from decoder: Decoder) throws
  {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    do
    {
      //no error here anymore
      location = try values.decode(MyGeoPoint.self, forKey: .geoloc) 
    }
    catch
    {
      print("data has location in server response\n")
    }

  }

  func encode(to encoder: Encoder) throws
  {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(location, forKey: .geoloc)

  }
}

Upvotes: 1

Related Questions