[Review] SE-0166: Swift Archival & Serialization

Forgive me if I'm missing something, this is a very large proposal and a
lot to parse through. I like how everything is designed as far as I can
tell except I'm not sure from this proposal how one would compose multiple
different types of Decoders against the same output type. For example, with
Location we have.

// Continuing examples from before; below is automatically generated by the
compiler if no customization is needed.
public struct Location : Codable {
private enum CodingKeys : CodingKey {
        case latitude
        case longitude
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try container.decode(Double.self, forKey: .latitude)
        longitude = try container.decode(Double.self, forKey: .longitude)
    }
}

However, this initializer seems strictly tied to the `CodingKeys` set of
coding keys. From what it appears, if this was used to decode from JSON the
format would have to always be:

{
"latitude" : 20.0
"longitude" : 20.0
}

I have a use case we're on my client we began the process of switching from
one version of an API to another. In one version we had a payload similar to

{
"user" : {
"uuid" : "uuid string..."
"can_send_message" : true
"can_delete_message" : false
}
}

this would result in the following Codable (from "user") from what I'm
following

public struct User : Codable {
private enum CodingKeys : CodingKey {
        case uuid
        case can_send_message
        case can_delete_message
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        uuid = try container.decode(Double.self, forKey: .uuid)
        canSendMessage = try container.decode(Bool.self, forKey:
.can_send_message)
        canDeleteMessage = try container.decode(Bool.self, forKey:
.can_delete_message)
    }
}

when we began switching over to the new api the payload was returned as

{
user {
"uuid" : "uuid string..."
"permissions" : {
"can_send_message" : true
"can_delete_message" : false
}
}
}

Here with "permissions" we have a new internal container with a separate
set of coding keys. Issue is in my use case I still need to maintain a way
to decode the old version simultaneously; we guard all new changes behind
feature flags in case something goes awry and we need to roll back our
changes.

How would one go about expressing this new Decoding and the previous
Decoding simultaneously on the same User type?

···

--

Rex Fenley | IOS DEVELOPER

Remind.com <https://www.remind.com/&gt; | BLOG <http://blog.remind.com/&gt;
> FOLLOW
US <https://twitter.com/remindhq&gt; | LIKE US
<https://www.facebook.com/remindhq&gt;

Hi Rex,

Forgive me if I'm missing something, this is a very large proposal and a lot to parse through. I like how everything is designed as far as I can tell except I'm not sure from this proposal how one would compose multiple different types of Decoders against the same output type. For example, with Location we have.

// Continuing examples from before; below is automatically generated by the compiler if no customization is needed.
public struct Location : Codable {
  private enum CodingKeys : CodingKey {
        case latitude
        case longitude
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try container.decode(Double.self, forKey: .latitude)
        longitude = try container.decode(Double.self, forKey: .longitude)
    }
}

However, this initializer seems strictly tied to the `CodingKeys` set of coding keys. From what it appears, if this was used to decode from JSON the format would have to always be:

{
  "latitude" : 20.0
  "longitude" : 20.0
}

I have a use case we're on my client we began the process of switching from one version of an API to another. In one version we had a payload similar to

{
  "user" : {
    "uuid" : "uuid string..."
    "can_send_message" : true
    "can_delete_message" : false
  }
}

this would result in the following Codable (from "user") from what I'm following

public struct User : Codable {
  private enum CodingKeys : CodingKey {
        case uuid
        case can_send_message
        case can_delete_message
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        uuid = try container.decode(Double.self, forKey: .uuid)
        canSendMessage = try container.decode(Bool.self, forKey: .can_send_message)
        canDeleteMessage = try container.decode(Bool.self, forKey: .can_delete_message)
    }
}

when we began switching over to the new api the payload was returned as

{
  user {
    "uuid" : "uuid string..."
    "permissions" : {
      "can_send_message" : true
      "can_delete_message" : false
    }
  }
}

Here with "permissions" we have a new internal container with a separate set of coding keys. Issue is in my use case I still need to maintain a way to decode the old version simultaneously; we guard all new changes behind feature flags in case something goes awry and we need to roll back our changes.

How would one go about expressing this new Decoding and the previous Decoding simultaneously on the same User type?

You would fall out of the automatically-generated scenario here, but this use case is exactly what the ‘container’ API is for. You can create a container with a new set of keys, then decode from it.

Another approach would be to create a nested type that conforms with Codable which represents the “permissions” as a type of its own.

- Tony

···

On Apr 10, 2017, at 12:48 PM, Rex Fenley via swift-evolution <swift-evolution@swift.org> wrote:

--
Rex Fenley | IOS DEVELOPER

Remind.com <https://www.remind.com/&gt; | BLOG <http://blog.remind.com/&gt; | FOLLOW US <https://twitter.com/remindhq&gt; | LIKE US <Facebook
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

How exactly does that work from the perspective of the initializer if you
have 2 sets of keys to choose from? Would I extend the initializer to
include my feature flag `public init(from decoder: Decoder, flag: Bool)
throws` and switch on that? Or could I pass in the Container I want to use
somehow, which seems cleaner to me as an abstraction? (This is similar to
what we're already doing with the setup we have.) How does a key jump
multiple levels "permissions.can_send_message" and is there a way to know
ahead of time which key maps to what property? Needing to express every
possible mapping from the initializer seems clumsy to me and I think would
fit better as a part of the Container itself.

···

On Mon, Apr 10, 2017 at 3:13 PM, Tony Parker <anthony.parker@apple.com> wrote:

Hi Rex,

On Apr 10, 2017, at 12:48 PM, Rex Fenley via swift-evolution < > swift-evolution@swift.org> wrote:

Forgive me if I'm missing something, this is a very large proposal and a
lot to parse through. I like how everything is designed as far as I can
tell except I'm not sure from this proposal how one would compose multiple
different types of Decoders against the same output type. For example, with
Location we have.

// Continuing examples from before; below is automatically generated by
the compiler if no customization is needed.
public struct Location : Codable {
private enum CodingKeys : CodingKey {
        case latitude
        case longitude
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try container.decode(Double.self, forKey: .latitude)
        longitude = try container.decode(Double.self, forKey: .longitude)
    }
}

However, this initializer seems strictly tied to the `CodingKeys` set of
coding keys. From what it appears, if this was used to decode from JSON the
format would have to always be:

{
"latitude" : 20.0
"longitude" : 20.0
}

I have a use case we're on my client we began the process of switching
from one version of an API to another. In one version we had a payload
similar to

{
"user" : {
"uuid" : "uuid string..."
"can_send_message" : true
"can_delete_message" : false
}
}

this would result in the following Codable (from "user") from what I'm
following

public struct User : Codable {
private enum CodingKeys : CodingKey {
        case uuid
        case can_send_message
        case can_delete_message
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        uuid = try container.decode(Double.self, forKey: .uuid)
        canSendMessage = try container.decode(Bool.self, forKey:
.can_send_message)
        canDeleteMessage = try container.decode(Bool.self, forKey:
.can_delete_message)
    }
}

when we began switching over to the new api the payload was returned as

{
user {
"uuid" : "uuid string..."
"permissions" : {
"can_send_message" : true
"can_delete_message" : false
}
}
}

Here with "permissions" we have a new internal container with a separate
set of coding keys. Issue is in my use case I still need to maintain a way
to decode the old version simultaneously; we guard all new changes behind
feature flags in case something goes awry and we need to roll back our
changes.

How would one go about expressing this new Decoding and the previous
Decoding simultaneously on the same User type?

You would fall out of the automatically-generated scenario here, but this
use case is exactly what the ‘container’ API is for. You can create a
container with a new set of keys, then decode from it.

Another approach would be to create a nested type that conforms with
Codable which represents the “permissions” as a type of its own.

- Tony

--
Rex Fenley | IOS DEVELOPER

Remind.com <https://www.remind.com/&gt; | BLOG <http://blog.remind.com/&gt; |
FOLLOW US <https://twitter.com/remindhq&gt; | LIKE US
<Facebook;
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--

Rex Fenley | IOS DEVELOPER

Remind.com <https://www.remind.com/&gt; | BLOG <http://blog.remind.com/&gt;
> FOLLOW
US <https://twitter.com/remindhq&gt; | LIKE US
<Facebook;