Swift GEOJson Codable object - swift

I am trying to access coordinates within a GEOJson I receive. I created Codable Structs.
My GEOJson codable Geometry struct is like this:
struct ServiceArea : Codable {
let area : [Area]?
let vehicleType : String?
enum CodingKeys: String, CodingKey {
case area = "area"
case vehicleType = "vehicle_type"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
area = try values.decodeIfPresent([Area].self, forKey: .area)
vehicleType = try values.decodeIfPresent(String.self, forKey: .vehicleType)
}
}
struct Area : Codable {
let geometry : Geometry?
let properties : Property?
let type : String?
enum CodingKeys: String, CodingKey {
case geometry
case properties
case type = "type"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
geometry = try Geometry(from: decoder)
properties = try Property(from: decoder)
type = try values.decodeIfPresent(String.self, forKey: .type)
}
}
struct Geometry : Codable {
let coordinates : [[[Float]]]?
let type : String?
enum CodingKeys: String, CodingKey {
case coordinates = "coordinates"
case type = "type"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
coordinates = try values.decodeIfPresent([[[Float]]].self, forKey: .coordinates)
type = try values.decodeIfPresent(String.self, forKey: .type)
}
}
But when I try to access .coordinates, it is always nil.

Related

How to encode enum with Arrays of Custom Objects

Thanks for your help in advance! I am relatively new to SwiftUI and have been struggling with encoding an enum with Arrays of custom objects. Here is the code:
struct ChartData: Codable {
var data: DataType
...
private enum CodingKeys : String, CodingKey { case id, title, type, info, label_x, label_y, last_update, data }
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
...
try container.encode(self.data, forKey: .data)
}
}
enum DataType: Codable{
case plot([ChartPlotData])
case slice([ChartSliceData])
case number([ChartNumberData])
}
struct ChartPlotData: Codable {
var chart_id: String
var order_plot: Int
var label_plot: String?
var points_x: [String]
var points_y: [Double]
private enum CodingKeys : String, CodingKey { case chart_id, order_plot, label_plot, points_x, points_y }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.chart_id = try container.decode(String.self, forKey: .chart_id)
self.order_plot = try container.decode(Int.self, forKey: .order_plot)
self.label_plot = try? container.decode(String.self, forKey: .label_plot)
do{
self.points_x = try container.decode([String].self, forKey:.points_x)
}catch{
let xs = try container.decode([Double].self, forKey:.points_x)
self.points_x = xs.map { String($0) }
}
self.points_y = try container.decode([Double].self, forKey:.points_y)
}
}
struct ChartSliceData: Codable {
var chart_id: String
var order_slice: Int
var label_slice: String
var value_slice: Double
}
struct ChartNumberData: Codable {
var chart_id: String
var number: Double
var unit: String?
}
I am attempting to cache JSON in UserDefaults so as to avoid having to make extraneous API calls. Using JSONEncoder, I am left with the following JSON snippet (excerpted from a much longer string):
"data" : {
"plot" : {
"_0" : [
{
"label_plot" : "China",
"points_y" : [
0,
0,
0,
...
However, I am looking to get an encoding like this:
"data" : [
{
"label_plot" : "China",
"points_y" : [
0,
0,
0,
...
Any help would be greatly appreciated! Thanks so much!
This can be solved by adding an extra item in the CodingKeys enum for encoding and decoding the type used for the DataType enum and then using a switch to encode and decode the right type of array.
Here is the full ChartData struct although with some properties and code removed for brevity
struct ChartData: Codable {
var id: Int
var title: String
var data: DataType
enum CodingKeys: String, CodingKey {
case id, title, data, type
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(title, forKey: .title)
switch data {
case .number(let values):
try container.encode("number", forKey: .type)
try container.encode(values, forKey: .data)
case .plot(let values):
try container.encode("plot", forKey: .type)
try container.encode(values, forKey: .data)
case .slice(let values):
try container.encode("slice", forKey: .type)
try container.encode(values, forKey: .data)
}
}
init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "number":
let values = try container.decode([ChartNumberData].self, forKey: .data)
data = .number(values)
// case ...
default:
fatalError("Unsupported type for DataType: \(type)")
}
}
}

Using Decodable with injected properties

Is there any way to use Decodable with injected property?
final class Score: Decodable {
let value: Int?
let uniqueId: String
convenience init(from decoder: Decoder/*, uniqueId: String*/) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try container.decodeIfPresent(Int.self, forKey: .value).flatMap { value = $0 }
// self.uniqueId = uniqueId
[... other properties parsing ...]
}
}
Example call:
final class Exam {
let identifier: Int
let scores: [Score]
convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
identifier = try container.decode(Int.self, forKey: .identifier)
scores = try container.decode([Score].self, forKey: .scores)
// I need to pass Exam's identifier to `score` on init, because it will generate Score's `uniqueId `
[... other properties parsing ...]
}
}
That would end with error with missing uniqueId, which I need to have after init but it's not in the JSON. Since it's identifier, making it optional and setting outside is not a proper way to handle it.
I'd love to inject it the way it's commented above, but how to do it?
There is no way to extend the initialiser because it's being called indirectly and there is no API provided to extend it. Thus, there are several ways to bypass it:
BEST: Inject the value into Decoder's userInfo if possible.
Create separate class for Response and separate for model. Example below.
Use plain JSONSerialization instead of Decodable.
As #JoakimDanielson suggested, create random identifier inside default initialiser. The issue is that it's not reproducable, so in case you're saving it to the DB, you'll always override the data, since the ID with each parsing would be different.
Example for approach 2:
final class ScoreResponse: Decodable {
let value: Int?
convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try container.decodeIfPresent(Int.self, forKey: .value).flatMap { value = $0 }
[... other properties parsing ...]
}
}
final class Score {
let value: Int?
let uniqueId: String
convenience init(from response: ScoreResponse, uniqueId: String) {
self.value = response.value // etc with other properties
self.uniqueId = uniqueId
}
}
final class Exam: Decodable {
let identifier: String
let scores: [Score] = []
convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
identifier = try container.decode(String.self, forKey: .identifier)
try container.decodeIfPresent([ScoreResponse].self, forKey: .scores).forEach {
scores.append({ Score(from: $0, uniqueId: identifier) })
}
}

Swift - Codable Decode array of arrays of CLLocation

I have a struct that contains an array of arrays of CLLocations. This is to support a multipolyline (in other words, a bunch of potentially discontiguous lines). I wish to encode and decode this data. I am having trouble writing the encoding and decoding methods as CLLocation is not codable by default.
struct MyTrack {
let coords: [[CLLocation]]?
enum CodingKeys: String, CodingKey {
case coords
}
}
extension MyTrack: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
coords = try values.decodeIfPresent([[CLLocation]].self, forKey: .coords)?
.map { ($0 as AnyObject).map { CLLocation(model: $0) } }
}
}
Its currently throwing two errors in Xcode:
Cannot convert value of type '[[CLLocation]].Type' to expected argument type '[Any?].Type'
Value of type 'AnyObject' has no member 'map'
Any help much appreciated!
Because CLLocation is not Codable by default, I followed a tutorial to create a wrapper struct around it, the code goes like this:
extension CLLocation: Encodable {
enum CodingKeys: String, CodingKey {
case latitude
case longitude
case altitude
case horizontalAccuracy
case verticalAccuracy
case speed
case course
case timestamp
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(coordinate.latitude, forKey: .latitude)
try container.encode(coordinate.longitude, forKey: .longitude)
try container.encode(altitude, forKey: .altitude)
try container.encode(horizontalAccuracy, forKey: .horizontalAccuracy)
try container.encode(verticalAccuracy, forKey: .verticalAccuracy)
try container.encode(speed, forKey: .speed)
try container.encode(course, forKey: .course)
try container.encode(timestamp, forKey: .timestamp)
}
}
struct Location: Codable {
let latitude: CLLocationDegrees
let longitude: CLLocationDegrees
let altitude: CLLocationDistance
let horizontalAccuracy: CLLocationAccuracy
let verticalAccuracy: CLLocationAccuracy
let speed: CLLocationSpeed
let course: CLLocationDirection
let timestamp: Date
}
extension CLLocation {
convenience init(model: Location) {
self.init(coordinate: CLLocationCoordinate2DMake(model.latitude, model.longitude), altitude: model.altitude, horizontalAccuracy: model.horizontalAccuracy, verticalAccuracy: model.verticalAccuracy, course: model.course, speed: model.speed, timestamp: model.timestamp)
}
}
You are decoding CLLocation, not your wrapper struct. You should decode your wrapper struct instead. Also, you shouldn't cast to AnyObject.
extension MyTrack: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
coords = try values.decodeIfPresent([[Location]].self, forKey: .coords)?
.map { $0.map(CLLocation.init) }
}
}

Decoding swift inheritance data

I have a Process class that has a Step type parameter (Step is "abstract"). I have two Step type classes: StepSign and StepReview, as shown in the code:
class Process: Codable {
let steps: Array<Step>
let actualStep: Step
private enum CodingKeys: String, CodingKey {
case steps, actualStep
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
steps = try container.decode(Array<Step>.self, forKey: .steps)
actualStep = try container.decode(Step.self, forKey: .actualStep)
}
}
class Step: Codable {
var id : Int?
}
class StepSign : Step {
var name : String?
private enum CodingKeys: String, CodingKey {
case name
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
try super.init(from: decoder)
}
}
class StepReview: Step {
var isReview: Bool
private enum CodingKeys: String, CodingKey {
case isReview
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
isReview = try container.decode(Bool.self, forKey: .isReview)
try super.init(from: decoder)
}
When I try to decode I get the Step, but I would like it to return either StepSign or StepReview. But I don't know how to indicate what type it is since at the time of decoding I don't know.
The problem I have is in the following lines:
steps = try container.decode(Array<Step>.self, forKey: .steps)
actualStep = try container.decode(Step.self, forKey: .actualStep)
Any ideas?

How to create model for this json with codable

I have the below json and I want to create model for the json with codable.
{
id = 1;
name = "abc";
empDetails = {
data = [{
address = "xyz";
ratings = 2;
"empId" = 6;
"empName" = "def";
}];
};
}
Model
struct Root: Codable {
let id: Int
let name: String
let empDetails:[Emp]
struct Emp: Codable {
let address: String
let ratings: Int
let empId: Int
let empName: String
}
}
I don't need the key data. I want to set the value of data to empDetails property
How can I do this with init(from decoder: Decoder) throws method?
Simply create enum CodingKeys and implement init(from:) in struct Root to get that working.
struct Root: Decodable {
let id: Int
let name: String
let empDetails: [Emp]
enum CodingKeys: String, CodingKey {
case id, name, empDetails, data
}
struct Emp: Codable {
let address: String
let ratings: Int
let empId: Int
let empName: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
let details = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .empDetails)
empDetails = try details.decode([Emp].self, forKey: .data)
}
}