diff --git a/Example/Tests/PerformanceTest.swift b/Example/Tests/PerformanceTest.swift index 27189cd..eef68e7 100644 --- a/Example/Tests/PerformanceTest.swift +++ b/Example/Tests/PerformanceTest.swift @@ -67,6 +67,35 @@ class PerformanceTest: XCTestCase { } } } + // 【1000】 average: 0.040, relative standard deviation: 19.680%, values: [0.055929, 0.044426, 0.028143, 0.040032, 0.041667, 0.042019, 0.039634, 0.026208, 0.038857, 0.040737] + func testEncodeWithJSONSerialization() throws { + let decoder = SmartJSONDecoder() + let objects = try decoder.decode([SmartModel].self, from: data) + measure { + do { + let encoder = SmartJSONEncoder() + encoder.generatorMode = .system + let _ = try encoder.encode(objects) + } catch { + XCTAssertNil(error) + } + } + } + + // 【1000】average: 0.039, relative standard deviation: 15.440%, values: [0.048204, 0.044045, 0.038840, 0.039561, 0.040793, 0.042170, 0.027042, 0.041110, 0.040413, 0.029381] + func testEncodeWithWriter() throws { + let decoder = SmartJSONDecoder() + let objects = try decoder.decode([SmartModel].self, from: data) + measure { + do { + let encoder = SmartJSONEncoder() + encoder.generatorMode = .custom + let _ = try encoder.encode(objects) + } catch { + XCTAssertNil(error) + } + } + } } // Codable & CleanJSON diff --git a/Example/Tests/Tests.swift b/Example/Tests/Tests.swift index 7e4df69..6080a03 100644 --- a/Example/Tests/Tests.swift +++ b/Example/Tests/Tests.swift @@ -4,7 +4,7 @@ import HandyJSON import CleanJSON -let count = 10000 // or 1, 10, 100, 1000, 10000 +let count = 1000 // or 1, 10, 100, 1000, 10000 let data = airportsJSON(count: count) class Tests: XCTestCase { diff --git a/Sources/SmartCodable/Core/JSONEncoder/SmartJSONEncoder.swift b/Sources/SmartCodable/Core/JSONEncoder/SmartJSONEncoder.swift index c2a4d09..276628d 100644 --- a/Sources/SmartCodable/Core/JSONEncoder/SmartJSONEncoder.swift +++ b/Sources/SmartCodable/Core/JSONEncoder/SmartJSONEncoder.swift @@ -8,6 +8,16 @@ import Foundation /// `JSONEncoder` facilitates the encoding of `Encodable` values into JSON. open class SmartJSONEncoder: JSONEncoder, @unchecked Sendable { + + public enum Generator: Int, Sendable { + /// JSONSerialization + case system + + case custom + } + + /// Generator,default: JSONSerialization + public var generatorMode: Generator = .system open var smartKeyEncodingStrategy: SmartKeyEncodingStrategy = .useDefaultKeys open var smartDataEncodingStrategy: SmartDataEncodingStrategy = .base64 @@ -42,11 +52,38 @@ open class SmartJSONEncoder: JSONEncoder, @unchecked Sendable { /// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`. /// - throws: An error if any value throws an error during encoding. open override func encode(_ value: T) throws -> Data { - let value: JSONValue = try encodeAsJSONValue(value) - let writer = JSONValue.Writer(options: self.outputFormatting) - let bytes = writer.writeValue(value) - - return Data(bytes) + switch generatorMode { + case .system: + let jsonValue: JSONValue = try encodeAsJSONValue(value) + let jsonObject = jsonValue.toFoundation() + + do { + return try JSONSerialization.data(withJSONObject: jsonObject, options: self.outputFormatting.jsonSerializationOptions) + } catch { + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error)) + } + case .custom: + let value: JSONValue = try encodeAsJSONValue(value) + let writer = JSONValue.Writer(options: self.outputFormatting) + let bytes = writer.writeValue(value) + + return Data(bytes) + } + } + + func mapOutputFormatting(_ formatting: JSONEncoder.OutputFormatting) -> JSONSerialization.WritingOptions { + var options = JSONSerialization.WritingOptions() + + if formatting.contains(.prettyPrinted) { + options.insert(.prettyPrinted) + } + if formatting.contains(.sortedKeys) { + if #available(iOS 11.0, macOS 10.13, *) { + options.insert(.sortedKeys) + } + } + + return options } func encodeAsJSONValue(_ value: T) throws -> JSONValue { @@ -90,3 +127,17 @@ extension CodingUserInfoKey { static let useMappedKeys = CodingUserInfoKey.init(rawValue: "Stamrt.useMappedKeys") } +extension JSONEncoder.OutputFormatting { + var jsonSerializationOptions: JSONSerialization.WritingOptions { + var options: JSONSerialization.WritingOptions = [] + if contains(.prettyPrinted) { + options.insert(.prettyPrinted) + } + if self.contains(.sortedKeys) { + if #available(iOS 11.0, macOS 10.13, *) { + options.insert(.sortedKeys) + } + } + return options + } +} diff --git a/Sources/SmartCodable/Core/JSONValue/JSONValue.swift b/Sources/SmartCodable/Core/JSONValue/JSONValue.swift index f4113bd..be66cf8 100644 --- a/Sources/SmartCodable/Core/JSONValue/JSONValue.swift +++ b/Sources/SmartCodable/Core/JSONValue/JSONValue.swift @@ -41,6 +41,30 @@ enum JSONValue: Equatable { } } + func toFoundation() -> Any { + switch self { + case .null: + return NSNull() + case .bool(let b): + return b + case .number(let n): + // 这里判断 string 是否能转成 Int 或 Double + if let intValue = Int(n) { + return intValue + } else if let doubleValue = Double(n) { + return doubleValue + } else { + return n + } + case .string(let s): + return s + case .array(let arr): + return arr.map { $0.toFoundation() } + case .object(let dict): + return dict.mapValues { $0.toFoundation() } + } + } + static func from(_ any: Any) throws -> JSONValue { switch any { case let dict as [String: Any]: