# Codable synthesis for enums with associated values

**URL:** <https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493>\
**Category:** Pitches\
**Tags:** codable\
**Created:** [October 27, 2020, 12:17am UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493 "2020-10-27T00:17:07Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![drexin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/drexin/32/7573_2.png) [@drexin](https://forums.swift.org/u/drexin)\
**Post date:** [October 27, 2020, 12:17am UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493/1 "2020-10-27T00:17:07Z")

</div>

### Introduction

Codable was introduced in [SE-0166](https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md)  
with support for synthesizing `Encodable` and `Decodable` conformance for  
`class` and `struct` types, that only contain values that also conform  
to the respective protocols.

### Motivation

Currently auto-synthesis does not work for enums with associated values.  
There have been discussions about it in the past, but the concrete structure  
of the encoded values was never agreed upon. We believe that having a solution  
for this is an important quality of life improvement.

### Proposed solution

The following enum with associated values

```swift
enum Command: Codable {
  case load(key: String)
  case store(key: String, value: Int)
}

```

would be encoded to

```json
{
  "load": {
    "key": "MyKey"
  }
}

```

and

```json
{
  "store": {
    "key": "MyKey",
    "value": 42
  }
}

```

The top-level container contains a single key that matches the name of the enum  
case that points to another container that contains the values as they would be  
encoded for structs and classes.

Associated values can also be unnamed, in which case they will be encoded into an  
array instead (that need to happen even if only one of the value is not named):

```swift
enum Command: Codable {
  case load(String)
  case store(String, value: Int)
}

```

would encoded to

```json
{
  "load": [
    "MyKey"
  ]
}

```

and

```json
{
  "store": [
    "MyKey",
    42
  ]
}

```

This solution is closely following the default behavior of the Rust library [serde](https://serde.rs/container-attrs.html).

---

<div class="post-metadata">

**Author:** ![itaiferber](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/itaiferber/32/33840_2.png) [@itaiferber](https://forums.swift.org/u/itaiferber)\
**Post date:** [October 27, 2020, 1:06am UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493/2 "2020-10-27T01:06:39Z")

</div>

Happy to see a pitch in this area! I think this is a good start in the direction of having _something_ in place for `enum`s which has been missing for a long time. I'm wondering about a few cases here which are not explicitly discussed in the pitch:

1. What about `enum` cases without associated values? Are those still encoded as keyed containers, or single values? For instance, `.load` in

2. What about `enum` cases that share a name? For instance, both cases of

3. It would be nice to see some Alternatives Considered/Future Directions with some discussion about other approaches. For instance, from a lot of discussion in [Automatic Codable conformance for enums with associated values that themselves conform to Codable](https://forums.swift.org/t/automatic-codable-conformance-for-enums-with-associated-values-that-themselves-conform-to-codable/11499) and similar threads, we know that different folks have differing opinions on what they would expect to be a default implementation based on their needs.

---

<div class="post-metadata">

**Author:** ![drexin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/drexin/32/7573_2.png) [@drexin](https://forums.swift.org/u/drexin)\
**Post date:** [October 27, 2020, 6:59pm UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493/3 "2020-10-27T18:59:45Z")

</div>

Those are very good points, thanks for bringing them up.

1. I think right now we have the following options:

- Use the same structure as for the other cases, i.e. `{ "load": {} }` or `{ "load": [] }`
- Make it a string, i.e. `"load"`

Using the default structure would certainly make it easier for the code generation and also there is currently no way to check if a key within a container points to a value or a nested container. So in this case we would need to use `container.nestedContainer` and catch the error if it doesn't exist. It would certainly be nicer to have a version of this function that returns an optional, so maybe that is something to consider adding.

I think one thing that speaks against using raw values as the default behavior is consistency.

1. I'm not sure there is a good way to represent this and I'm also not sure this is a very common case for serialization. I'm open for suggestions on this one, but I am leaning towards disallowing this for auto-synthesis for now.

2. I'll add some thoughts on the alternatives to the pitch later. Thanks for pointing this out.

---

<div class="post-metadata">

**Author:** ![itaiferber](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/itaiferber/32/33840_2.png) [@itaiferber](https://forums.swift.org/u/itaiferber)\
**Post date:** [October 27, 2020, 8:20pm UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493/4 "2020-10-27T20:20:08Z")

</div>

Thanks for the consideration! I think there's a lot of potential here. 😄 Some additional thoughts:

> [@drexin](#):
>
> Using the default structure would certainly make it easier for the code generation and also there is currently no way to check if a key within a container points to a value or a nested container. So in this case we would need to use `container.nestedContainer` and catch the error if it doesn't exist.

I'm not entirely sure what you mean by this; do you mind elaborating? As in, determining the difference between `"load"` and `{"load": ...}` directly from the `Decoder` instance itself, or something else?

> [@drexin](#):
>
> I think one thing that speaks against using raw values as the default behavior is consistency.

Although I agree that consistency is definitely nice, one thing to consider is the evolution of `enum`s over time. Code which uses an `enum` like

```swift
enum Foo: Codable {
    case bar
    case baz
}

```

If it later adds a

```swift
case quux(String)

```

it would no longer be able to decode previously-encoded `.bar` and `.baz` values, which _could_ have encoded as single values. It might be surprising that the addition of one `enum` case with associated values would change the encoded representation of all of the other values too (and potentially silently at that).

> [@drexin](#):
>
> I'm not sure there is a good way to represent this and I'm also not sure this is a very common case for serialization. I'm open for suggestions on this one, but I am leaning towards disallowing this for auto-synthesis for now.

Although I've seen `enum`s like this in the wild, I mostly brought this up as something worth explicitly calling out in the pitch and the implementation. I don't necessarily think there needs to be a different representation for these two cases, just clearly spelled-out rules about what would happen (e.g., `enum` cases like this are attempted in order, always). I think disallowing these altogether might introduce a bit more pain than is needed, but just my opinion.

---

<div class="post-metadata">

**Author:** ![drexin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/drexin/32/7573_2.png) [@drexin](https://forums.swift.org/u/drexin)\
**Post date:** [October 27, 2020, 9:15pm UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493/5 "2020-10-27T21:15:39Z")

</div>

> [@itaiferber](#):
>
> I'm not entirely sure what you mean by this; do you mind elaborating? As in, determining the difference between `"load"` and `{"load": ...}` directly from the `Decoder` instance itself, or something else?

Yes, sorry. I mean if I have a `KeyedDecodingContainer`, I can't determine whether under a given key, there is a nested container in a non-throwing manner. For values there are the `decodeIfPresent` functions, for nested containers there is only the throwing function.

> [@itaiferber](#):
>
> Although I agree that consistency is definitely nice, one thing to consider is the evolution of `enum`s over time. Code which uses an `enum` like
> 
> ```swift
> enum Foo: Codable {
> case bar
> case baz
> }
> 
> ```
> 
> If it later adds a
> 
> ```swift
> case quux(String)
> 
> ```
> 
> it would no longer be able to decode previously-encoded `.bar` and `.baz` values, which _could_ have encoded as single values. It might be surprising that the addition of one `enum` case with associated values would change the encoded representation of all of the other values too (and potentially silently at that).

Yes, that is another point in favor of always using containers instead of raw values.

> [@itaiferber](#):
>
> Although I've seen `enum`s like this in the wild, I mostly brought this up as something worth explicitly calling out in the pitch and the implementation. I don't necessarily think there needs to be a different representation for these two cases, just clearly spelled-out rules about what would happen (e.g., `enum` cases like this are attempted in order, always). I think disallowing these altogether might introduce a bit more pain than is needed, but just my opinion.

I think disallowing this would be better than silently running into the wrong case. If we can find a good way to represent these cases in the future, the support can still be added, but it's hard to change it, once it's in.

---

<div class="post-metadata">

**Author:** ![itaiferber](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/itaiferber/32/33840_2.png) [@itaiferber](https://forums.swift.org/u/itaiferber)\
**Post date:** [October 28, 2020, 3:14pm UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493/6 "2020-10-28T15:14:33Z")

</div>

> [@drexin](#):
>
> Yes, sorry. I mean if I have a `KeyedDecodingContainer` , I can't determine whether under a given key, there is a nested container in a non-throwing manner. For values there are the `decodeIfPresent` functions, for nested containers there is only the throwing function.

Got it, agreed. The way to do this would be to attempt to fetch a nested container and catch an error if that failed. Synthesis could at least be smart about this and attempt only the containers expected based on the types of `enum` cases (e.g. if all `enum` cases are labeled, there's no need to attempt an unkeyed container).

> [@drexin](#):
>
> Yes, that is another point in favor of always using containers instead of raw values.

I was going to say that I'm not sure I agree (I was trying to make the case for the opposite side) but realized that my concern is unfounded. To be a bit clearer, I was concerned that

1. Attempting to encode `enum Foo: Codable` _today_ would encode `.bar` and `.baz` as simple strings, but with this proposal, the encoding format would change
2. Along those lines, adding another case with associated values (not possible today) could risk changing the encoding format

I realized after posting that `enum Foo: Codable` as expressed above doesn't work today because `Foo` is not `RawRepresentable`, and if `Foo` were made `RawRepresentable` to compile today, you couldn't add a case with associated values without dropping the raw value anyway.

So to sum up:

1. Having cases with no associated values encode as keyed containers won't change any behavior from today
2. Having the, encode as keyed containers wouldn't change the encoding format if you add additional cases with associated values

I'm on board with this direction.

---

<div class="post-metadata">

**Author:** ![swhitty](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/swhitty/32/10082_2.png) [@swhitty](https://forums.swift.org/u/swhitty)\
**Post date:** [November 14, 2020, 12:05am UTC](https://forums.swift.org/t/codable-synthesis-for-enums-with-associated-values/41493/7 "2020-11-14T00:05:16Z")

</div>

Considering the similarities to tuples, it may make sense to reduce the scope to only provide synthesis for cases with single associated values. I would still find this very useful.

```swift
enum Action: Codable {
  case close
  case web(Auth)
  case web(link: Link)

  // do not support until tuples also have a synthesis solution 
  case both(auth: Auth, link: Link)
}

struct Auth: Codable { }
struct Link: Codable { }

```

Developers would naturally expect to be able to override the synthesis of `CodingKeys` like they can today with structs;

```swift
enum Action: Codable {
  case close
  case web(Auth)
  case web(link: Link)

  enum CodingKeys: String, CodingKey {
    case web = "auth"
    case webLink = "link"
  }
}

```
