0

I need something like the following:

enum SomeEnum: Bool, Codable {
  case accept = true
  case reject = false
}

struct SomeStruct {
  var someEnum: SomeEnum
}

let s: SomeStruct = ...
print(try! try! JSONEncoder().encode(s)) //{"someEnum": true}

Problem 1: Bool type can't be simply applied to enums. Resolved here: Raw type 'Bool' is not expressible by any literal

Problem 2: Instead of {"someEnum": true} console log shows {"someEnum": {"true": {}} - unresolved. How to fix it correctly? Is it possible to solve by editing SomeEnum code only (because this enum may be used in other places).

Gargo
  • 1,135
  • 1
  • 10
  • 21
  • 1
    Declaring an enum with a `Bool` rawValue seems like a strange choice - why not just use an appropriately named `Bool` variable instead? Instead of declaring an enum with `accept = true` and `reject = false` cases, a `var didAccept: Bool` would be a cleaner choice IMHO. – Dávid Pásztor Mar 15 '23 at 14:21
  • 1
    What should happen if you add a third `case`? How should it be decoded? – Joakim Danielson Mar 15 '23 at 14:23
  • @DávidPásztor the problem is with existing server API and previously it was resolved by avoiding `Codable` at all. `didAccept == true` is ok but `didAccept == false` is more like `accept == nil` so it is not suitable – Gargo Mar 15 '23 at 16:08
  • That doesn't make sense. Your enum property is not optional in your example. Also, you still only have 2 enum cases, so you can definitely represent all possible values using a `Bool` instead. If in reality, your enum property is optional, you could just use an optional bool property. – Dávid Pásztor Mar 15 '23 at 16:48

2 Answers2

1

So I tried your code with minimal fixes to pass the compilation... and it produces the output you expect. Fixes:

// Fix 1: Follow advice from another SO thread to get Bool enum to conform to RawRepresentable
extension Bool: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: Int) {
        self = value != 0
    }
}

enum SomeEnum: Bool, Codable, RawRepresentable {
  case accept = true
  case reject = false
}

// Fix 2: Mark SomeStruct as Codable
struct SomeStruct: Codable {
  var someEnum: SomeEnum
}

// Fix 3: Make example to be actually executable
let s: SomeStruct = SomeStruct(someEnum: .accept)
let encoded = try JSONEncoder().encode(s)
print(String(data: encoded, encoding: .utf8)!)

And the example prints out:

{"someEnum":true}

(This should be a comment really, as it doesn't solve anything, but won't fit in the comment)

timbre timbre
  • 12,648
  • 10
  • 46
  • 77
  • thanks. The only problem I see is `Bool` extension is public. It means now code `let b: Bool = 1` will work too. Also `RawRepresentable` mention is redundant. – Gargo Mar 16 '23 at 05:17
1

Here is an alternative approach.

import Foundation

enum SomeEnum
{
    case accept
    case reject

    var rawValue: Bool { self == .accept }
    init(rawValue: Bool)
    {
        self = rawValue ? .accept : .reject
    }
}

extension SomeEnum: Codable
{
    init(from decoder: Decoder) throws
    {
        let container = try decoder.singleValueContainer()
        let value = try container.decode(Bool.self)
        self.init(rawValue: value)
    }

    func encode(to encoder: Encoder) throws
    {
        var container = try encoder.singleValueContainer()
        try container.encode(self.rawValue)
    }
}


struct SomeStruct: Codable {
  var someEnum: SomeEnum
}

let s: SomeStruct = SomeStruct(someEnum: .accept)
print(try! String(bytes: JSONEncoder().encode(s), encoding: .utf8) ?? "null") //{"someEnum": true}

let decoder = JSONDecoder()
let s2 = try decoder.decode(SomeStruct.self, from: #"{"someEnum" : false}"#.data(using: .utf8)!)

print("\(s2)") // SomeStruct(someEnum: __lldb_expr_118.SomeEnum.reject)

This effectively "emulates" a raw representable enum without jumping through the hoops of making Bool usable as one.

JeremyP
  • 84,577
  • 15
  • 123
  • 161