Home > database >  Issue converting custom type array into data | Swift
Issue converting custom type array into data | Swift

Time:10-21

Essentially I would like to convert an array of the following custom struct into data for data for easier saving in CoreData as Binary Data. How can the following be converted into data to then be ready to decode back:

Custom Struct

struct Place: Codable, Identifiable {
    var id = UUID()
    var coordinate: Coordinate
    
    struct Coordinate: Codable {
        let latitude: Double
        let longitude: Double

        func locationCoordinate() -> CLLocationCoordinate2D {
            return CLLocationCoordinate2D(latitude: self.latitude,
                                          longitude: self.longitude)
        }
    }
}

Adding to Custom Struct

var mapAddresses = [Place]()

Task {
  mapAddresses.append(Place(coordinate: try await getCoordinate(from: 
  post.location)))
}

The issue I am having is converting the array mapAddresses with the custom structure into Binary Data, that can then be decoded back into the custom array.

CodePudding user response:

In comments, your phrasing indicates that you want a binary encoding. I'll get to that, but first let's just encode/decode JSON.

Given

struct Place: Codable {...}

let mapAddresses: [Place] = ...

The following code encodes mapAddresses, then immediately decodes the resulting Data:

guard let data = try? JSONEncoder().encode(mapAddresses) else {
    fatalError("Failed to encode")
}

guard let decodedMapAddresses = 
    try? JSONDecoder().decode([Place].self, from: data) 
else { fatalError("Failed to decode") }

Whether data is binary is a matter of interpretation. data will just contain the text of the JSON representing mapAddresses. Technically text is binary, but then again so is anything else in a computer. What we normally mean is non-text, that is some more compact or more directly machine-friendly encoding that is not so human-friendly. JSONEncoder doesn't provide such a facility, but PropertyListEncoder does, via it's outputFormat property. To use it:

let encoder = PropertlyListEncoder()
encoder.outputFormat = .binary

guard let data = encoder.encode(mapAddresses) else {
    fatalError("Failed to encode")
}

guard let decodedMapAddresses = 
    try? PropertyListDecoder().decode([Place].self, from: data)
else { fatalError("Failed to decode") }

Note there is no need specify that it's binary in the decoder, because PropertyListDecoder knows how to detect that.

CodePudding user response:

Like others have said in the comments, you don't have a fully-formed question yet. But first, you don't need Coordinate. Just make CLLocationCoordinate2D Codable, like Apple should have.

import struct CoreLocation.CLLocationCoordinate2D

public extension CLLocationCoordinate2D {
  enum CodingKey: Swift.CodingKey {
    case latitude
    case longitude
  }
}

extension CLLocationCoordinate2D: Decodable {
  public init(from decoder: Decoder) throws {
    try self.init(
      Self.init, (CodingKey.latitude, .longitude),
      decoder: decoder
    )
  }
}

extension CLLocationCoordinate2D: Encodable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKey.self)
    try container.encode(latitude, forKey: .latitude)
    try container.encode(longitude, forKey: .longitude)
  }
}
public extension Decodable {
  /// Initialize using 2 keyed, decoded arguments.
  /// - Parameters:
  ///   - init: An initializer (or  factory function) whose arguments are the decoded values.
  ///   - keys: `CodingKey` instances, matching the arguments.
  init<
    Parameter0: Decodable, Parameter1: Decodable, Key: CodingKey
  >(
    _ init: (Parameter0, Parameter1) -> Self,
    _ keys: (Key, Key),
    decoder: Decoder
  ) throws {
    let container = try decoder.container(keyedBy: Key.self)
    self = try `init`(
      container.decode(forKey: keys.0),
      container.decode(forKey: keys.1)
    )
  }
}
public extension KeyedDecodingContainerProtocol {
  /// Decode, relying on the return type, to avoid having to explicitly use a metatype argument.
  func decode<Decodable: Swift.Decodable>(forKey key: Key) throws -> Decodable {
    try decode(Decodable.self, forKey: key)
  }
}
  • Related