Skip to content

EricRabil/Swexy

Repository files navigation

Swexy

Swift, but Sexy.

Motivation

Swift Foundation leaves a lot to be desired. You often have to write tons and tons of boilerplate, and don't get me started on the Codable system.

Swexy includes common extension patterns I find myself writing across projects, and I hope others can find value in them.

Current Extensions

  • KeyPath-based reducing into dictionaries, with support for compactMap-style omission of optional keys/values ([(abc: 123, def: "ghi")].dictionary(keyedBy: \.def, valuedBy: \.abc))
  • Sorting an array by a KeyPath ([Person].sorted(usingKey: .age, by: >), [Person].sorted(usingKey: .age, withDefaultValue: 0, by: >)
  • Flatten a collection of collections into an array of its elements (Collection.flatten())
  • Anonymous Coding

Anonymous Coding

Sometimes – no, not sometimes, many times – you run into data structures that either cannot be made codable because they reside in another module, or require significant boilerplate to write codable conformance for.

Swexy adds two new methods:

public extension JSONEncoder {
    func encode(_ cb: @escaping (Encoder) throws -> ()) throws -> Data
}
public extension JSONDecoder {
    func decode<P>(data: Data, _ cb: @escaping (Decoder) throws -> P) throws -> P
}

These allow you to quickly encode and decode datatypes without hacking in extensions. For instance, you cannot conform an external structure to a protocol, so making it codable requires some wrappers that get a bit hairy.

Here's an example:

Somewhere in another module

struct MyArbitraryUncodableStruct {
    let aBool: Bool
    let aInt: Int
}

Your code

let instance = MyArbitraryUncodableStruct(aBool: true, aInt: 54)

enum UncodableCodingKeys: CodingKey {
    case aBool, aInt
}
    
let data = try JSONEncoder().encode { encoder in
    var container = encoder.container(keyedBy: UncodableCodingKeys.self)
    
    try container.encode(instance.aBool, forKey: .aBool)
    try container.encode(instance.aInt, forKey: .aInt)
}

let parsed = try JSONDecoder().decode(data: data) { decoder -> MyArbitraryUncodableStruct in
    let container = try decoder.container(keyedBy: UncodableCodingKeys.self)
    
    return MyArbitraryUncodableStruct(
        aBool: try container.decode(Bool.self, forKey: .aBool),
        aInt: try container.decode(Int.self, forKey: .aInt)
    )
}

print(data)
print(String(decoding: data, as: UTF8.self))
print(parsed.aBool)
print(parsed.aInt)

It should be noted that this should be avoided wherever possible, but I personally prefer it over writing messy coding systems for things like associated-value enums.

About

Foundation extensions I always write in projects

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages