Adding conformance to ExpressibleByArrayLiteral and ExpressibleByStringLiteral

I have this code I'm working on to facilitate interpreting JSON "null" values as empty strings or arrays so that the corresponding properties don't have to be optional:

@propertyWrapper
struct NullToEmpty<T>: Codable where T: Codable & EmptyConstructible {
    let wrappedValue: T

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.wrappedValue = container.decodeNil() ? T() : try container.decode(T.self)
    }

    func encode(to encoder: Encoder) throws {
        try wrappedValue.encode(to: encoder)
    }
}
protocol EmptyConstructible { init() }
extension String: EmptyConstructible {}
extension Array: EmptyConstructible {}

I originally had separate types for the string and array cases, but I thought I could consolidate them.

The problem I'm running into is adding conformance to ExpressibleByArrayLiteral and ExpressibleByStringLiteral, with each having different issues.

For ExpressibleByArrayLiteral, I don't know how to define the extension for the generic Array case. I can do this:

extension NullToEmpty: ExpressibleByArrayLiteral where T == Array<Int> {
    init(arrayLiteral elements: T.ArrayLiteralElement...) {
        self.wrappedValue = T(elements)
    }
}

..but I want to get rid of Int there, of course, and the compiler won't let me just say Array. So how do I express that? I also tried where T: ExpressibleByArrayLiteral but that runs into the issue of forwarding variadic arguments, which AFAICT you can't do in Swift.

For the string case, I tried this:

extension NullToEmpty: ExpressibleByStringLiteral where T == String {
    init(stringLiteral value: String) {
        self.wrappedValue = value
    }
}

..but the compiler complains that it doesn't also conform to ExpressibleByUnicodeScalarLiteral and ExpressibleByExtendedGraphemeClusterLiteral. The latter seems particularly tricky because I'm supposed to have an associated type that conforms to _ExpressibleByBuiltinExtendedGraphemeClusterLiteral. I don't think I want to mess with that underscore. Is there a way to make that work, or to make those conformances more implicit?

I don't think you need to conform the wrapper type to ExpressibleBy…Literal. It's the wrapped value that needs to conform to it. You can implement init(wrappedValue:) to have the wrapped types in the autogenerated initializer of the Codable types involving @NullToEmpty:

@propertyWrapper
struct NullToEmpty<T>: Codable where T: Codable & EmptyConstructible {
  let wrappedValue: T

  init(wrappedValue value: T) {
    wrappedValue = value
  }

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    self.wrappedValue = container.decodeNil() ? T() : try container.decode(T.self)
  }

  func encode(to encoder: Encoder) throws {
    try wrappedValue.encode(to: encoder)
  }
}
struct MyData: Codable {
  @NullToEmpty var string: String
  @NullToEmpty var array: [Double]
}

let myData = MyData(string: "", array: [])
2 Likes

Ah, that's much simpler. Thanks!