这是 TableSchema 的 Swift 语言实现,用于定义处理表格数据的模式。
表格数据的模式定义了类型,施加了约束,并在字段上创建了外键关系,数据值从某种物理表示形式移动到逻辑表示形式,反之亦然时生效。 例如,一个存储的 CSV 文件(物理)可以与相应的模式描述符一起加载到内存中,以便从字符串值转换为 Swift 标准库类型,如 Date
或 Int
(逻辑)。
swift-corelibs-foundation
中可用的功能Foundation
框架虽然不完整,但该库正在至少一个发布的产品中使用,并涵盖了可用功能的子集。 不支持的字段类型将不会被转换,导致在这些不支持的类型上可能会丢失数据。 请参考下表。 有一个测试套件来检查哪些功能应该工作正常。 欢迎贡献。
功能 | 状态 |
---|---|
迭代时的流式传输和类型转换 | 可用 |
类型转换字段类型和格式 | 部分可用 |
[反]序列化 | 在表格数据包中可用 |
模式推断 | 缺失(不太可能实现) |
严格模式 | 缺失 |
约束验证 | 缺失 |
外键验证 | 缺失 |
富 (RDF) 类型 | 缺失(不太可能实现) |
类型 | 格式 | 附加属性 | 正向状态(物理到逻辑) | 反向状态(逻辑到物理) |
---|---|---|---|---|
string | default, uri, binary, uuid | N/A | 可用 | 可用 |
string | 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()