是否可以仅为枚举创建自定义 Swift KeyEncodingStrategy?
Is it possible to create custom Swift KeyEncodingStrategy for enums only?
2019 年 6 月 20 日更新:感谢@rudedog,我找到了一个可行的解决方案。我在原来的 post...
下面附加了实现
请注意,我不是在您的 struct/enum 声明中寻找“使用私有枚举 CodingKeys:String,CodingKey”。
我遇到的情况是,我调用的服务需要所有枚举的上 snake_case (UPPER_SNAKE_CASE
)。
给出以下 struct
:
public struct Request: Encodable {
public let foo: Bool?
public let barId: BarIdType
public enum BarIdType: String, Encodable {
case test
case testGroup
}
}
任何请求中的所有枚举都应转换为 UPPER_SNAKE_CASE。
例如,let request = Request(foo: true, barId: testGroup)
发送后应如下所示:
{
"foo": true,
"barId": "TEST_GROUP"
}
我想提供一个仅适用于 enum
类型的自定义 JSONEncoder.KeyEncodingStrategy
。
创建自定义策略似乎很简单,至少根据 Apple 的 JSONEncoder.KeyEncodingStrategy.custom(_:) 文档。
这是我目前的情况:
public struct AnyCodingKey : CodingKey {
public var stringValue: String
public var intValue: Int?
public init(_ base: CodingKey) {
self.init(stringValue: base.stringValue, intValue: base.intValue)
}
public init(stringValue: String) {
self.stringValue = stringValue
}
public init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
public init(stringValue: String, intValue: Int?) {
self.stringValue = stringValue
self.intValue = intValue
}
}
extension JSONEncoder.KeyEncodingStrategy {
static var convertToUpperSnakeCase: JSONEncoder.KeyEncodingStrategy {
return .custom { keys in // codingKeys is [CodingKey]
// keys = Enum ???
var key = AnyCodingKey(keys.last!)
// key = Enum ???
key.stringValue = key.stringValue.toUpperSnakeCase // toUpperSnakeCase is a String extension
return key
}
}
}
我一直在尝试确定 [CodingKey] 是否代表一个枚举,或者单个 CodingKey 是否代表一个枚举,因此应该成为 UPPER_SNAKE_CASE。
我知道这听起来毫无意义,因为我可以简单地提供硬编码的 CodingKeys,但是我们有很多服务调用,都需要对枚举案例进行相同的处理。为编码器指定自定义 KeyEncodingStrategy 会更简单。
理想的做法是在自定义策略中应用 JSONEncoder.KeyEncodingStrategy.convertToSnakeCase
,然后只应用 return 大写值。但同样,仅当该值代表枚举案例时。
有什么想法吗?
这是在@rudedog 的帮助下解决我的问题的代码:
import Foundation
public protocol UpperSnakeCaseRepresentable: Encodable {
var upperSnakeCaseValue: String { get }
}
extension UpperSnakeCaseRepresentable where Self: RawRepresentable, Self.RawValue == String {
var upperSnakeCaseValue: String {
return _upperSnakeCaseValue(rawValue)
}
}
extension KeyedEncodingContainer {
mutating func encode(_ value: UpperSnakeCaseRepresentable, forKey key: KeyedEncodingContainer<K>.Key) throws {
try encode(value.upperSnakeCaseValue, forKey: key)
}
}
// The following is copied verbatim from https://github.com/apple/swift/blob/master/stdlib/public/Darwin/Foundation/JSONEncoder.swift
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
// The only change is to call uppercased() on the encoded value as part of the return.
fileprivate func _upperSnakeCaseValue(_ stringKey: String) -> String {
guard !stringKey.isEmpty else { return stringKey }
var words : [Range<String.Index>] = []
// The general idea of this algorithm is to split words on transition from lower to upper case, then on transition of >1 upper case characters to lowercase
//
// myProperty -> my_property
// myURLProperty -> my_url_property
//
// We assume, per Swift naming conventions, that the first character of the key is lowercase.
var wordStart = stringKey.startIndex
var searchRange = stringKey.index(after: wordStart)..<stringKey.endIndex
// Find next uppercase character
while let upperCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
let untilUpperCase = wordStart..<upperCaseRange.lowerBound
words.append(untilUpperCase)
// Find next lowercase character
searchRange = upperCaseRange.lowerBound..<searchRange.upperBound
guard let lowerCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.lowercaseLetters, options: [], range: searchRange) else {
// There are no more lower case letters. Just end here.
wordStart = searchRange.lowerBound
break
}
// Is the next lowercase letter more than 1 after the uppercase? If so, we encountered a group of uppercase letters that we should treat as its own word
let nextCharacterAfterCapital = stringKey.index(after: upperCaseRange.lowerBound)
if lowerCaseRange.lowerBound == nextCharacterAfterCapital {
// The next character after capital is a lower case character and therefore not a word boundary.
// Continue searching for the next upper case for the boundary.
wordStart = upperCaseRange.lowerBound
} else {
// There was a range of >1 capital letters. Turn those into a word, stopping at the capital before the lower case character.
let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound)
words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
// Next word starts at the capital before the lowercase we just found
wordStart = beforeLowerIndex
}
searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
}
words.append(wordStart..<searchRange.upperBound)
let result = words.map({ (range) in
return stringKey[range].lowercased()
}).joined(separator: "_")
return result.uppercased()
}
enum Snake: String, UpperSnakeCaseRepresentable, Encodable {
case blackAdder
case mamba
}
struct Test: Encodable {
let testKey: String?
let snake: Snake
}
let test = Test(testKey: "testValue", snake: .mamba)
let enumData = try! JSONEncoder().encode(test)
let json = String(data: enumData, encoding: .utf8)!
print(json)
我认为您实际上是在寻找一种值编码策略?密钥编码策略改变了 keys 的编码方式,而不是它们的值的编码方式。值编码策略类似于 JSONDecoder
的 dateDecodingStrategy
,而您正在寻找一个用于枚举的策略。
这种方法可能适合您:
protocol UpperSnakeCaseRepresentable {
var upperSnakeCaseValue: String { get }
}
extension UpperSnakeCaseRepresentable where Self: RawRepresentable, RawValue == String {
var upperSnakeCaseValue: String {
// Correct implementation left as an exercise
return rawValue.uppercased()
}
}
extension KeyedEncodingContainer {
mutating func encode(_ value: UpperSnakeCaseRepresentable, forKey key: KeyedEncodingContainer<K>.Key) throws {
try encode(value.upperSnakeCaseValue, forKey: key)
}
}
enum Snake: String, UpperSnakeCaseRepresentable, Encodable {
case blackAdder
case mamba
}
struct Test: Encodable {
let snake: Snake
}
let test = Test(snake: .blackAdder)
let data = try! JSONEncoder().encode(test)
let json = String(data: data, encoding: .utf8)!
print(json)
现在,您声明为符合 UpperSnakeCaseRepresentable
的任何枚举都将按照您的需要进行编码。
您可以用同样的方法扩展其他编码和解码容器。
2019 年 6 月 20 日更新:感谢@rudedog,我找到了一个可行的解决方案。我在原来的 post...
下面附加了实现请注意,我不是在您的 struct/enum 声明中寻找“使用私有枚举 CodingKeys:String,CodingKey”。
我遇到的情况是,我调用的服务需要所有枚举的上 snake_case (UPPER_SNAKE_CASE
)。
给出以下 struct
:
public struct Request: Encodable {
public let foo: Bool?
public let barId: BarIdType
public enum BarIdType: String, Encodable {
case test
case testGroup
}
}
任何请求中的所有枚举都应转换为 UPPER_SNAKE_CASE。
例如,let request = Request(foo: true, barId: testGroup)
发送后应如下所示:
{
"foo": true,
"barId": "TEST_GROUP"
}
我想提供一个仅适用于 enum
类型的自定义 JSONEncoder.KeyEncodingStrategy
。
创建自定义策略似乎很简单,至少根据 Apple 的 JSONEncoder.KeyEncodingStrategy.custom(_:) 文档。
这是我目前的情况:
public struct AnyCodingKey : CodingKey {
public var stringValue: String
public var intValue: Int?
public init(_ base: CodingKey) {
self.init(stringValue: base.stringValue, intValue: base.intValue)
}
public init(stringValue: String) {
self.stringValue = stringValue
}
public init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
public init(stringValue: String, intValue: Int?) {
self.stringValue = stringValue
self.intValue = intValue
}
}
extension JSONEncoder.KeyEncodingStrategy {
static var convertToUpperSnakeCase: JSONEncoder.KeyEncodingStrategy {
return .custom { keys in // codingKeys is [CodingKey]
// keys = Enum ???
var key = AnyCodingKey(keys.last!)
// key = Enum ???
key.stringValue = key.stringValue.toUpperSnakeCase // toUpperSnakeCase is a String extension
return key
}
}
}
我一直在尝试确定 [CodingKey] 是否代表一个枚举,或者单个 CodingKey 是否代表一个枚举,因此应该成为 UPPER_SNAKE_CASE。
我知道这听起来毫无意义,因为我可以简单地提供硬编码的 CodingKeys,但是我们有很多服务调用,都需要对枚举案例进行相同的处理。为编码器指定自定义 KeyEncodingStrategy 会更简单。
理想的做法是在自定义策略中应用 JSONEncoder.KeyEncodingStrategy.convertToSnakeCase
,然后只应用 return 大写值。但同样,仅当该值代表枚举案例时。
有什么想法吗?
这是在@rudedog 的帮助下解决我的问题的代码:
import Foundation
public protocol UpperSnakeCaseRepresentable: Encodable {
var upperSnakeCaseValue: String { get }
}
extension UpperSnakeCaseRepresentable where Self: RawRepresentable, Self.RawValue == String {
var upperSnakeCaseValue: String {
return _upperSnakeCaseValue(rawValue)
}
}
extension KeyedEncodingContainer {
mutating func encode(_ value: UpperSnakeCaseRepresentable, forKey key: KeyedEncodingContainer<K>.Key) throws {
try encode(value.upperSnakeCaseValue, forKey: key)
}
}
// The following is copied verbatim from https://github.com/apple/swift/blob/master/stdlib/public/Darwin/Foundation/JSONEncoder.swift
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
// The only change is to call uppercased() on the encoded value as part of the return.
fileprivate func _upperSnakeCaseValue(_ stringKey: String) -> String {
guard !stringKey.isEmpty else { return stringKey }
var words : [Range<String.Index>] = []
// The general idea of this algorithm is to split words on transition from lower to upper case, then on transition of >1 upper case characters to lowercase
//
// myProperty -> my_property
// myURLProperty -> my_url_property
//
// We assume, per Swift naming conventions, that the first character of the key is lowercase.
var wordStart = stringKey.startIndex
var searchRange = stringKey.index(after: wordStart)..<stringKey.endIndex
// Find next uppercase character
while let upperCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
let untilUpperCase = wordStart..<upperCaseRange.lowerBound
words.append(untilUpperCase)
// Find next lowercase character
searchRange = upperCaseRange.lowerBound..<searchRange.upperBound
guard let lowerCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.lowercaseLetters, options: [], range: searchRange) else {
// There are no more lower case letters. Just end here.
wordStart = searchRange.lowerBound
break
}
// Is the next lowercase letter more than 1 after the uppercase? If so, we encountered a group of uppercase letters that we should treat as its own word
let nextCharacterAfterCapital = stringKey.index(after: upperCaseRange.lowerBound)
if lowerCaseRange.lowerBound == nextCharacterAfterCapital {
// The next character after capital is a lower case character and therefore not a word boundary.
// Continue searching for the next upper case for the boundary.
wordStart = upperCaseRange.lowerBound
} else {
// There was a range of >1 capital letters. Turn those into a word, stopping at the capital before the lower case character.
let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound)
words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
// Next word starts at the capital before the lowercase we just found
wordStart = beforeLowerIndex
}
searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
}
words.append(wordStart..<searchRange.upperBound)
let result = words.map({ (range) in
return stringKey[range].lowercased()
}).joined(separator: "_")
return result.uppercased()
}
enum Snake: String, UpperSnakeCaseRepresentable, Encodable {
case blackAdder
case mamba
}
struct Test: Encodable {
let testKey: String?
let snake: Snake
}
let test = Test(testKey: "testValue", snake: .mamba)
let enumData = try! JSONEncoder().encode(test)
let json = String(data: enumData, encoding: .utf8)!
print(json)
我认为您实际上是在寻找一种值编码策略?密钥编码策略改变了 keys 的编码方式,而不是它们的值的编码方式。值编码策略类似于 JSONDecoder
的 dateDecodingStrategy
,而您正在寻找一个用于枚举的策略。
这种方法可能适合您:
protocol UpperSnakeCaseRepresentable {
var upperSnakeCaseValue: String { get }
}
extension UpperSnakeCaseRepresentable where Self: RawRepresentable, RawValue == String {
var upperSnakeCaseValue: String {
// Correct implementation left as an exercise
return rawValue.uppercased()
}
}
extension KeyedEncodingContainer {
mutating func encode(_ value: UpperSnakeCaseRepresentable, forKey key: KeyedEncodingContainer<K>.Key) throws {
try encode(value.upperSnakeCaseValue, forKey: key)
}
}
enum Snake: String, UpperSnakeCaseRepresentable, Encodable {
case blackAdder
case mamba
}
struct Test: Encodable {
let snake: Snake
}
let test = Test(snake: .blackAdder)
let data = try! JSONEncoder().encode(test)
let json = String(data: data, encoding: .utf8)!
print(json)
现在,您声明为符合 UpperSnakeCaseRepresentable
的任何枚举都将按照您的需要进行编码。
您可以用同样的方法扩展其他编码和解码容器。