forked from iAmMccc/SmartCodable
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSmartJSONEncoder.swift
More file actions
143 lines (116 loc) · 5.67 KB
/
Copy pathSmartJSONEncoder.swift
File metadata and controls
143 lines (116 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import Foundation
//===----------------------------------------------------------------------===//
// JSON Encoder
//===----------------------------------------------------------------------===//
/// `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
/// Options set on the top-level encoder to pass down the encoding hierarchy.
struct _Options {
let dateEncodingStrategy: DateEncodingStrategy
let dataEncodingStrategy: SmartDataEncodingStrategy
let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy
let keyEncodingStrategy: SmartKeyEncodingStrategy
let userInfo: [CodingUserInfoKey: Any]
}
/// The options set on the top-level encoder.
fileprivate var options: _Options {
return _Options(dateEncodingStrategy: .secondsSince1970,
dataEncodingStrategy: smartDataEncodingStrategy,
nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
keyEncodingStrategy: smartKeyEncodingStrategy,
userInfo: userInfo)
}
// MARK: - Encoding Values
/// Encodes the given top-level value and returns its JSON representation.
///
/// - parameter value: The value to encode.
/// - returns: A new `Data` value containing the encoded JSON data.
/// - 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<T: Encodable>(_ value: T) throws -> Data {
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<T: Encodable>(_ value: T) throws -> JSONValue {
let encoder = JSONEncoderImpl(options: self.options, codingPath: [])
guard let topLevel = try encoder.wrapEncodable(value, for: nil) else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
}
return topLevel
}
}
//===----------------------------------------------------------------------===//
// Error Utilities
//===----------------------------------------------------------------------===//
extension EncodingError {
/// Returns a `.invalidValue` error describing the given invalid floating-point value.
///
///
/// - parameter value: The value that was invalid to encode.
/// - parameter path: The path of `CodingKey`s taken to encode this value.
/// - returns: An `EncodingError` with the appropriate path and debug description.
fileprivate static func _invalidFloatingPointValue<T: FloatingPoint>(_ value: T, at codingPath: [CodingKey]) -> EncodingError {
let valueDescription: String
if value == T.infinity {
valueDescription = "\(T.self).infinity"
} else if value == -T.infinity {
valueDescription = "-\(T.self).infinity"
} else {
valueDescription = "\(T.self).nan"
}
let debugDescription = "Unable to encode \(valueDescription) directly in JSON. Use SmartJSONEncoder.NonConformingFloatEncodingStrategy.convertToString to specify how the value should be encoded."
return .invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: debugDescription))
}
}
extension CodingUserInfoKey {
/// 是否使用映射之后的key
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
}
}