tableschema-swift

Build Coverage Codebase Support

这是 TableSchema 的 Swift 语言实现,用于定义处理表格数据的模式。

表格数据的模式定义了类型,施加了约束,并在字段上创建了外键关系,数据值从某种物理表示形式移动到逻辑表示形式,反之亦然时生效。 例如,一个存储的 CSV 文件(物理)可以与相应的模式描述符一起加载到内存中,以便从字符串值转换为 Swift 标准库类型,如 DateInt(逻辑)。

要求

实现状态

虽然不完整,但该库正在至少一个发布的产品中使用,并涵盖了可用功能的子集。 不支持的字段类型将不会被转换,导致在这些不支持的类型上可能会丢失数据。 请参考下表。 有一个测试套件来检查哪些功能应该工作正常。 欢迎贡献。

功能状态

功能 状态
迭代时的流式传输和类型转换 可用
类型转换字段类型和格式 部分可用
[反]序列化 在表格数据包中可用
模式推断 缺失(不太可能实现)
严格模式 缺失
约束验证 缺失
外键验证 缺失
富 (RDF) 类型 缺失(不太可能实现)

类型转换字段类型和格式状态

类型 格式 附加属性 正向状态(物理到逻辑) 反向状态(逻辑到物理)
string default, uri, binary, uuid N/A 可用 可用
string email N/A 不可用 不可用
number N/A 任何 不可用 不可用
integer N/A bareNumber = false 可用* 可用
integer N/A bareNumber = true 可用 可用
boolean N/A trueValues, falseValues 可用 可用
object N/A N/A 可用 不可用
array N/A N/A 可用 可用
date N/A default 可用* 不可用
date N/A any, pattern 不可用 不可用
time N/A default 可用* 不可用
time N/A any, pattern 不可用 不可用
datetime N/A default 可用* 可用*
datetime N/A any, pattern 不可用 不可用
year N/A N/A 可用* 不可用
yearmonth N/A N/A 可用* 不可用
duration N/A N/A 可用* 不可用
geopoint default, array, object N/A 可用 不可用
geojson default, topo N/A 不可用 不可用
any N/A N/A 不可用 不可用

* 仅在 Apple 产品(即 iOS 和 macOS)上可用,因为 swift-corelibs-foundation 中的实现不完整

集成到您的项目中

本项目使用 Swift Package Manager 设置。 理想情况下,将其添加到项目的 SPM 依赖项中,或使用 Xcode 集成的 Swift Package Manager。 或者,生成您自己的 Xcode .xcodeproj 以使用以下命令与您的构建系统集成

swift package generate-xcodeproj --xcconfig-overrides ./Configuration.xcconfig

使用示例

从数据源进行迭代时的类型转换

通过使用提供行信息的 TableProvider 数据源设置一个 Table 即可完成数据(例如,从 CSV 文件)的反序列化。 这允许数据源流式传输数据,而不必将所有内容加载到内存中。 Table 与特定的数据源无关,但期望数据源转换为 String 表示形式。

let sourcePath = "import.csv"
let sourceDialect = DialectalCSV.Dialect()

let fields = [Field("name", type: .string), Field("birthday", type: .date)]
let schema = Schema(fields)

guard let provider = MyTableProvider(atPath: sourcePath, dialect: sourceDialect) else {
    fatalError("Oops")
}
let table = Table(provider: AnyTableProvider(provider), schema: schema)
let objects = table.map { $0 }

并定义 MyTableProvider 以及 CSV 解析库,如 DialectalCSV

class MyTableProvider: TableProvider {

    private let handler: DialectalCSV.InputHandler
    private let streamIterator: DialectalCSV.InputIterator

    init?(atPath path: String, dialect: DialectalCSV.Dialect) {
        guard let handler = DialectalCSV.InputHandler(atPath: path, dialect: dialect) else {
            return nil
        }
        self.handler = handler
        self.streamIterator = handler.makeIterator()
    }

    // MARK: - TableProvider

    var header: Header? {
        return self.streamIterator.header
    }

    // MARK: - Sequence

    func makeIterator() -> AnyIterator<[String?]> {
        return AnyIterator {
            return self.streamIterator.next()
        }
    }

}

反向类型转换(逻辑到物理)

在内存中转换整个数据集

let objects = [[Any?]]()
let rows = objects.map { schema.reverseCast(row: $0) }

或者使用像 DialectalCSV 这样的 CSV 解析库进行流式输出

let objects: [[Any?]] = [["River Tam", Date(timeIntervalSince1970: 16725225600)],["Simon Tam", nil]]

let destinationPath = "export.csv"
var destinationDialect = DialectalCSV.Dialect()
destinationDialect.nullSequence = "null"

FileManager.default.createFile(atPath: destinationPath, contents: nil)
guard let outputHandler = DialectalCSV.OutputHandler(atPath: destinationPath, dialect: destinationDialect) else {
    fatalError("Oops")
}

let header = schema.fields.map { $0.name }
try? outputHandler.open(header: header)

for object in objects {
    let row = schema.reverseCast(row: object).map { $0 }
    try? outputHandler.append(records: [row])
}

try? outputHandler.close()