Kitura

APIDoc Build Status - Master macOS Linux Apache 2 Slack Status

TypeDecoder

TypeDecoder 是一个 Swift 库,允许检查 Swift 语言的原生和复杂类型。 它最初是为了在 Kitura 项目中使用而编写的,但可以很容易地用于各种需要动态检查类型的项目。

Swift 版本

TypeDecoder 的最新版本需要 Swift 4.0 或更高版本。 您可以通过此 链接 下载此版本的 Swift 二进制文件。 不保证与其他 Swift 版本的兼容性。

用法

添加依赖项

TypeDecoder 添加到应用程序的 Package.swift 文件中的依赖项中。 将 "x.x.x" 替换为最新的 TypeDecoder 版本

.package(url: "https://github.com/Kitura/TypeDecoder.git", from: "x.x.x")

TypeDecoder 添加到目标依赖项

.target(name: "example", dependencies: ["TypeDecoder"]),

使用 TypeDecoder

TypeDecoder.decode() 返回一个 TypeInfo 枚举,该枚举描述传递给 decode() 的类型。 TypeInfo 枚举值是

对于这些枚举值中的每一个,第一个参数始终是传递给 TypeDecoder 进行解码的原始类型。

例子

要使用 TypeDecoder,您需要声明一个符合 Decodable 协议的类型,例如下面的 struct StructureType

import TypeDecoder

struct StructureType: Decodable {
    let aString: String
}

然后,您调用 TypeDecoder.decode() 并传递要解码的类型。 如果您打印返回的 TypeInfo 枚举的内容,您可以看到它描述了传递给 decode() 的类型。

let structureTypeInfo = try TypeDecoder.decode(StructureType.self)
print("\(structureTypeInfo)")

打印 structureTypeInfo 将输出

StructureType{
  aString: String
}

然后,您可以使用 .keyed(Any.Type, [String: TypeInfo]) 来获取包含在键控结构中的所有字段的 Dictionary,这允许您按名称检索每个字段的 TypeInfo

在下面的示例中,字段 "aString" 的类型是一个基本类型,因此您可以使用 .single(Any.Type, Any.Type)。 要调用的适当枚举值将根据字段类型而有所不同,请参阅下面的完整实现,了解如何解码更复杂的类型。

if case .keyed(_, let dict) = structureTypeInfo {

  print("\nThe Dictionary returned from decoding StructureType contains:\n\(dict)")

  /// Fields that are not containers will be .single
  if let theTypeInfo = dict["aString"] {
    if case .single(_) = theTypeInfo {
      print("aString is type \(theTypeInfo)")
    }
  }
}

您将看到以下输出

The Dictionary returned from decoding StructureType contains:
OrderedDictionary<String, TypeInfo>(keys: ["aString"], values: ["aString": String], iteratorCount: 1)
aString is type String

完整实现

/// Building and running this example shows how to decode a Swift data structure.
///
/// You should expect to see the following output:
///
/// Print the returned TypeInfo and you get this:
/// StructType{
///   myString: String,
///   myOptional: Float?,
///   myCyclic: [StructType{<cyclic>}],
///   myDict: [String:Bool],
///   myArray: [Int8]
/// }
///
/// The Dictionary returned from decoding StructType contains:
/// ["myString": String, "myOptional": Float?, "myCyclic": [StructType{<cyclic>}], "myDict": [String:Bool], "myArray": [Int8]]
///
/// Each field can be individually inspected:
/// myString is type String
/// myDict is type Dictionary<String, Bool>
/// myArray contains type Int8
/// myOptional is type Float
///
/// Cyclics are harder to deal with as they're buried inside an Array type:
/// myCyclic is type StructType

import TypeDecoder

struct StructType: Decodable {
    let myString: String
    let myDict: Dictionary<String, Bool>
    let myArray: [Int8]
    let myOptional: Float?
    let myCyclic: [StructType]
}

func main() {
    do {
        let structTypeInfo = try TypeDecoder.decode(StructType.self)
        /// Print the returned TypeInfo and you get this:
        ///
        /// StructType{
        ///   myString: String,
        ///   myDict: [String:Bool],
        ///   myArray: [Int8],
        ///   myOptional: Float?,
        ///   myCyclic: [StructType{<cyclic>}]
        /// }
        print("Print the returned TypeInfo and you get this:\n\(structTypeInfo)")

        if case .keyed(_, let dict) = structTypeInfo {
            /// .keyed TypeInfo contains a Dictionary<String, TypeInfo> of all fields contained in
            /// the keyed structure, so each field's TypeInfo can be retrieved by name.
            print("\nThe Dictionary returned from decoding StructType contains:\n\(dict)")
            print("\nEach field can be individually inspected:")

            /// Fields that are not containers will be .single
            if let theTypeInfo = dict["myString"] {
                if case .single(_) = theTypeInfo {
                    print("myString is type \(theTypeInfo)")
                }
            }

            /// .dynamicKeyed fields are Dictionary types
            if let theTypeInfo = dict["myDict"] {
                if case .dynamicKeyed(_, let keyTypeInfo, let valueTypeInfo) = theTypeInfo {
                    print("myDict is type Dictionary<\(keyTypeInfo), \(valueTypeInfo)>")
                }
            }

            /// .unkeyed fields are Array or Set types
            if let theTypeInfo = dict["myArray"] {
                if case .unkeyed(_, let theTypeInfo) = theTypeInfo {
                    print("myArray contains type \(theTypeInfo)")
                }
            }

            /// .optional field types
            if let theTypeInfo = dict["myOptional"] {
                if case .optional(let theTypeInfo) = theTypeInfo {
                    print("myOptional is type \(theTypeInfo)")
                }
            }

            /// .cyclic fields are embedded inside .unkeyed (Array or Set) types  
            if let theTypeInfo = dict["myCyclic"] {
                print("\nCyclics are harder to deal with as they're buried inside an Array type:")
                if case .unkeyed(_, let theTypeInfo) = theTypeInfo {
                    if case .cyclic(let theTypeInfo) = theTypeInfo {
                        print("myCyclic is type \(theTypeInfo)")
                    }
                }
            }
        }
    } catch let error {
        print(error)
    }
}

main()

与执行验证的类型的兼容性

TypeDecoder 通过使用 Codable 框架来模拟类型的解码来工作。 为类型(以及任何嵌套类型)调用 init(from: Decoder) 初始化程序,以便发现其结构。 为了在没有序列化表示的情况下创建实例,解码器为每个字段提供虚拟值。

但是,在某些情况下,类型可能需要在初始化期间执行对这些值的验证。 TypeDecoder 提供了一种机制,用于通过 ValidSingleCodingValueProviderValidKeyedCodingValueProvider 协议在解码期间提供可接受的值。

ValidSingleCodingValueProvider

下面是一个 enum 的示例,其原始值为 String。 Swift 可以为此类类型合成 Codable 一致性,从而生成一个 init(from: Decoder),它需要一个与其中一个枚举用例匹配的有效字符串。 以下是如何扩展此类类型以使其与 TypeDecoder 兼容

public enum Fruit: String, Codable {
    case apple, banana, orange, pear
}

// Provide an acceptable value during decoding
extension Fruit: ValidSingleCodingValueProvider {
    public static func validCodingValue() -> Any? {
        // Returns the string "apple"
        return self.apple.rawValue
    }
}

ValidKeyedCodingValueProvider

结构化类型的示例,其中验证其中一个字段,以及使其能够由 TypeDecoder 处理的扩展

public class YoungAdult: Codable {
    let name: String
    let age: Int

    required public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: CodingKeys.name)
        self.age = try container.decode(Int.self, forKey: CodingKeys.age)
        // Validate the age field
        guard self.age >= 18, self.age <= 30 else {
            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Age is outside the permitted range"))
        }
    }
}

// Provide a value for 'age' which is within the acceptable range
extension YoungAdult: ValidKeyedCodingValueProvider {
    public static func validCodingValue(forKey key: CodingKey) -> Any? {
        switch key.stringValue {
        case self.CodingKeys.age.stringValue:
            return 20
        default:
            // For any fields that are not validated, you may return nil.
            // The TypeDecoder will use a standard dummy value.
            return nil
        }
    }
}

在 TypeDecoder 中实现的扩展 Foundation 类型的示例,该类型需要验证,并且使用数字 CodingKeys

extension URL: ValidKeyedCodingValueProvider {
    public static func validCodingValue(forKey key: CodingKey) -> Any? {
        switch key.intValue {
        case 1?: return "http://example.com/"
        default: return nil
        }
    }
}

API 文档

有关更多信息,请访问我们的 API 参考

社区

我们喜欢讨论服务器端 Swift 和 Kitura。 加入我们的 Slack 来认识团队!

许可证

该库在 Apache 2.0 下获得许可。 完整的许可证文本可在 LICENSE 中找到。