此包在 Swift 中提供了一个 json 编码器和解码器(不使用 Foundation 或任何其他依赖项)。该实现符合 RFC8259 规范。与 Linux 上的 Foundation 实现相比,它提供了显著的性能提升。
如果您喜欢在没有任何依赖项的情况下使用 Swift 的想法,您可能也会喜欢我们在 Swift 中重新实现的 Base64:swift-extras-base64
unsafe
Swift 语法Data
和 Date
的自定义编码器和解码器CodingKey
转换为 camelCase 或 snake_case(我想研究一下)SwiftNIO
。将 swift-extras-json
作为依赖项添加到您的 Package.swift
dependencies: [
.package(url: "https://github.com/swift-extras/swift-extras-json.git", .upToNextMajor(from: "0.6.0")),
],
将 ExtrasJSON
添加到您想要使用它的目标中。
targets: [
.target(name: "MyFancyTarget", dependencies: [
.product(name: "ExtrasJSON", package: "swift-extras-json"),
])
]
像使用 Foundation 编码器和解码器一样使用它。
import ExtrasJSON
let bytesArray = try XJSONEncoder().encode(myEncodable)
let myDecodable = try XJSONDecoder().decode(MyDecodable.self, from: bytes)
为了获得最佳性能,请从您的 ByteBuffer
创建一个 [UInt8]
,即使 buffer.readableBytesView
在技术上也可以工作。
let result = try XJSONDecoder().decode(
[SampleStructure].self,
from: buffer.readBytes(length: buffer.readableBytes)!)
let bytes = try XJSONEncoder().encode(encodable)
var buffer = byteBufferAllocator.buffer(capacity: bytes.count)
buffer.writeBytes(bytes)
通过使用 swift-extras-json
而不是默认的 Foundation 实现,来提高您的 Vapor 4 API 的性能。首先,您需要按照 Vapor 文档 中的描述,实现对 Vapor 的 ContentEncoder
和 ContentDecoder
的一致性。
import Vapor
import ExtrasJSON
extension XJSONEncoder: ContentEncoder {
public func encode<E: Encodable>(
_ encodable: E,
to body: inout ByteBuffer,
headers: inout HTTPHeaders) throws
{
headers.contentType = .json
let bytes = try self.encode(encodable)
// the buffer's storage is resized in case its capacity is not sufficient
body.writeBytes(bytes)
}
}
extension XJSONDecoder: ContentDecoder {
public func decode<D: Decodable>(
_ decodable: D.Type,
from body: ByteBuffer,
headers: HTTPHeaders) throws -> D
{
guard headers.contentType == .json || headers.contentType == .jsonAPI else {
throw Abort(.unsupportedMediaType)
}
var body = body
return try self.decode(D.self, from: body.readBytes(length: body.readableBytes)!)
}
}
接下来,注册编码器和解码器以在 Vapor 中使用
let decoder = XJSONDecoder()
ContentConfiguration.global.use(decoder: decoder, for: .json)
let encoder = XJSONEncoder()
ContentConfiguration.global.use(encoder: encoder, for: .json)
所有测试均在 2019 MacBook Pro (16 英寸 – 2.4 GHz 8 核 Intel Core i9) 上运行。您可以通过克隆此仓库并自行运行测试
# change dir to perf tests
$ cd PerfTests
# compile and run in release mode - IMPORTANT ‼️
$ swift run -c release
macOS Swift 5.1 | macOS Swift 5.2 | Linux Swift 5.1 | Linux Swift 5.2 | |
---|---|---|---|---|
Foundation | 2.61秒 | 2.62秒 | 13.03秒 | 12.52秒 |
ExtrasJSON | 1.23秒 | 1.25秒 | 1.13秒 | 1.05秒 |
加速 | ~2倍 | ~2倍 | ~10倍 | ~10倍 |
macOS Swift 5.1 | macOS Swift 5.2 | Linux Swift 5.1 | Linux Swift 5.2 | |
---|---|---|---|---|
Foundation | 2.72秒 | 3.04秒 | 10.27秒 | 10.65秒 |
ExtrasJSON | 1.70秒 | 1.72秒 | 1.39秒 | 1.16秒 |
加速 | ~1.5倍 | ~1.5倍 | ~7倍 | ~8倍 |
Date 和 Data 是编码和解码的特殊情况。它们确实有默认的、有点特殊的实现
Date 将被编码为浮点数
示例:2020-03-17 16:36:58 +0000
将被编码为 606155818.503831
Data 将被编码为数字数组。
示例:0, 1, 2, 3, 255
将被编码为:[0, 1, 2, 3, 255]
是的,这是默认实现。只有 Apple 知道为什么它不是 ISO 8601 和 Base64。 🙃 由于我不想链接到 Foundation,因此无法像 Foundation 实现那样为 Date
和 Data
实现默认的编码和解码策略。因此,如果您想使用默认值以外的其他编码/解码策略,则需要覆盖 encode(to: Encoder)
和 init(from: Decoder)
。
这可能看起来像这样
struct MyEvent: Decodable {
let eventTime: Date
enum CodingKeys: String, CodingKey {
case eventTime
}
init(from decoder: Decoder) {
let container = try decoder.container(keyedBy: CodingKeys.self)
let dateString = try container.decode(String.self, forKey: .eventTime)
guard let timestamp = MyEvent.dateFormatter.date(from: dateString) else {
let dateFormat = String(describing: MyEvent.dateFormatter.dateFormat)
throw DecodingError.dataCorruptedError(forKey: .eventTime, in: container, debugDescription:
"Expected date to be in format `\(dateFormat)`, but `\(dateString) does not fulfill format`")
}
self.eventTime = timestamp
}
private static let dateFormatter: DateFormatter = MyEvent.createDateFormatter()
private static func createDateFormatter() -> DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}
}
您可以在 Apple 的文档中找到有关 编码和解码自定义类型 的更多信息。
当然,您可以使用 @propertyWrapper
来使其更优雅
import Foundation
@propertyWrapper
struct DateStringCoding: Decodable {
var wrappedValue: Date
init(wrappedValue: Date) {
self.wrappedValue = wrappedValue
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
guard let date = Self.dateFormatter.date(from: dateString) else {
let dateFormat = String(describing: Self.dateFormatter.dateFormat)
throw DecodingError.dataCorruptedError(in: container, debugDescription:
"Expected date to be in format `\(dateFormat)`, but `\(dateString) does not fulfill format`")
}
self.wrappedValue = date
}
private static let dateFormatter: DateFormatter = Self.createDateFormatter()
private static func createDateFormatter() -> DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}
}
struct MyEvent: Decodable {
@DateStringCoding
var eventTime: Date
}
在测试文件 DateCodingTests 中查看完整示例。
如果您的输入是 UTF-16 或 UTF-32 编码的,您可以轻松地将其转换为 UTF-8
let utf16 = UInt16[]() // your utf-16 encoded data
let utf8 = Array(String(decoding: utf16, as: Unicode.UTF16.self).utf8)
let utf32 = UInt32[]() // your utf-32 encoded data
let utf8 = Array(String(decoding: utf32, as: Unicode.UTF32.self).utf8)
请随时欢迎并鼓励您为 swift-extras-json
做出贡献。这是一个非常年轻的项目,非常欢迎您的帮助。
如果您发现错误、有建议或需要帮助入门,请打开 Issue 或 PR。如果您使用此包,我将感谢您分享您的经验。
当前关注领域
KeyEncodingStrategy
@propertyWrappers
的建议以及发现错别字。