Skip to content

Commit 65ef35b

Browse files
committed
Implement Data encoding/decoding strategies
1 parent 05586c9 commit 65ef35b

5 files changed

Lines changed: 136 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
272272
* Add `JSON.Decoder.keyDecodingStrategy`. This is very similar to Swift 4.1's `JSONDecoder.keyDecodingStrategy`, although by default it won't apply to decoding any values of type `JSON` or `JSONObject` (there's another option `applyKeyDecodingStrategyToJSONObject` that controls this).
273273
* Add `JSON.Encoder.dateEncodingStrategy`. This is very similar to `JSONEncoder.dateEncodingStrategy` except it includes another case for encoding ISO8601-formatted dates with fractional seconds (on Apple platforms).
274274
* Add `JSON.Decoder.dateDecodingStrategy`. This is very similar to `JSONDecoder.dateDecodingStrategy` except it includes another case for decoding ISO8601-formatted dates with fractional seconds (on Apple platforms).
275+
* Add `JSON.Encoder.dataEncodingStrategy`. This is identical to `JSONEncoder.dataEncodingStrategy`.
276+
* Add `JSON.Decoder.dataDecodingStrategy`. This is identical to `JSONDecoder.dataDecodingStrategy`.
275277

276278
#### v3.0.2 (2018-02-21)
277279

Sources/Coders/SwiftDecoder.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ extension JSON {
3838
/// The strategy to use in decoding dates. Defaults to `.deferredToDate`.
3939
public var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate
4040

41+
/// The strategy to use in decoding binary data. Defaults to `.base64`.
42+
public var dataDecodingStrategy: DataDecodingStrategy = .base64
43+
4144
/// Creates a new, reusable JSON decoder.
4245
public init() {}
4346

@@ -91,6 +94,7 @@ extension JSON {
9194
data.keyDecodingStrategy = keyDecodingStrategy
9295
data.applyKeyDecodingStrategyToJSONObject = applyKeyDecodingStrategyToJSONObject
9396
data.dateDecodingStrategy = dateDecodingStrategy
97+
data.dataDecodingStrategy = dataDecodingStrategy
9498
return try _JSONDecoder(data: data, value: json).decode(type)
9599
}
96100
}
@@ -102,6 +106,7 @@ private class DecoderData {
102106
var keyDecodingStrategy: JSON.Decoder.KeyDecodingStrategy = .useDefaultKeys
103107
var applyKeyDecodingStrategyToJSONObject = false
104108
var dateDecodingStrategy: JSON.Decoder.DateDecodingStrategy = .deferredToDate
109+
var dataDecodingStrategy: JSON.Decoder.DataDecodingStrategy = .base64
105110

106111
func copy() -> DecoderData {
107112
let result = DecoderData()
@@ -110,6 +115,7 @@ private class DecoderData {
110115
result.keyDecodingStrategy = keyDecodingStrategy
111116
result.applyKeyDecodingStrategyToJSONObject = applyKeyDecodingStrategyToJSONObject
112117
result.dateDecodingStrategy = dateDecodingStrategy
118+
result.dataDecodingStrategy = dataDecodingStrategy
113119
return result
114120
}
115121
}
@@ -364,6 +370,19 @@ extension _JSONDecoder: SingleValueDecodingContainer {
364370
case .custom(let decode):
365371
return try decode(self) as! T
366372
}
373+
case is Data.Type:
374+
switch _data.dataDecodingStrategy {
375+
case .deferredToData:
376+
break
377+
case .base64:
378+
let str = try wrapTypeMismatch(value.asJSON.getString())
379+
guard let data = Data(base64Encoded: str) else {
380+
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Encountered Data is not valid Base64.")
381+
}
382+
return data as! T
383+
case .custom(let decode):
384+
return try decode(self) as! T
385+
}
367386
default:
368387
break
369388
}
@@ -811,6 +830,18 @@ extension JSON.Decoder {
811830
/// Decode the `Date` as a custom value decoded by the given closure.
812831
case custom((_ decoder: Decoder) throws -> Date)
813832
}
833+
834+
/// The strategy to use for decoding `Data` values.
835+
public enum DataDecodingStrategy {
836+
/// Defer to `Data` for decoding.
837+
case deferredToData
838+
839+
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
840+
case base64
841+
842+
/// Decodes the `Data` as a custom value decoded by the given closure.
843+
case custom((_ decoder: Decoder) throws -> Data)
844+
}
814845
}
815846

816847
// MARK: -

Sources/Coders/SwiftEncoder.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ extension JSON {
201201
/// The strategy to use in encoding dates. Defaults to `.deferredToDate`.
202202
public var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate
203203

204+
/// The strategy to use in encoding binary data. Defaults to `.base64`.
205+
public var dataEncodingStrategy: DataEncodingStrategy = .base64
206+
204207
/// Creates a new, reusable JSON encoder.
205208
public init() {}
206209

@@ -252,6 +255,7 @@ extension JSON {
252255
data.keyEncodingStrategy = keyEncodingStrategy
253256
data.applyKeyEncodingStrategyToJSONObject = applyKeyEncodingStrategyToJSONObject
254257
data.dateEncodingStrategy = dateEncodingStrategy
258+
data.dataEncodingStrategy = dataEncodingStrategy
255259
let encoder = _JSONEncoder(data: data)
256260
try encoder.encode(value)
257261
guard let json = encoder.json else {
@@ -268,6 +272,7 @@ private class EncoderData {
268272
var keyEncodingStrategy: JSON.Encoder.KeyEncodingStrategy = .useDefaultKeys
269273
var applyKeyEncodingStrategyToJSONObject = false
270274
var dateEncodingStrategy: JSON.Encoder.DateEncodingStrategy = .deferredToDate
275+
var dataEncodingStrategy: JSON.Encoder.DataEncodingStrategy = .base64
271276

272277
var shouldRekeyJSONObjects: Bool {
273278
switch keyEncodingStrategy {
@@ -283,6 +288,7 @@ private class EncoderData {
283288
result.keyEncodingStrategy = keyEncodingStrategy
284289
result.applyKeyEncodingStrategyToJSONObject = applyKeyEncodingStrategyToJSONObject
285290
result.dateEncodingStrategy = dateEncodingStrategy
291+
result.dataEncodingStrategy = dataEncodingStrategy
286292
return result
287293
}
288294
}
@@ -529,6 +535,19 @@ extension _JSONEncoder: SingleValueEncodingContainer {
529535
self.json = .unboxed([:])
530536
}
531537
}
538+
case let data as Data:
539+
switch _data.dataEncodingStrategy {
540+
case .deferredToData:
541+
try value.encode(to: self)
542+
case .base64:
543+
try encode(data.base64EncodedString())
544+
case .custom(let f):
545+
try f(data, self)
546+
if self.value?.isEmpty ?? true {
547+
// the function didn't encode anything
548+
self.json = .unboxed([:])
549+
}
550+
}
532551
default:
533552
try value.encode(to: self)
534553
}
@@ -953,4 +972,19 @@ extension JSON.Encoder {
953972
/// an empty object in its place.
954973
case custom((Date, Encoder) throws -> Void)
955974
}
975+
976+
/// The strategy to use for encoding `Data` values.
977+
public enum DataEncodingStrategy {
978+
/// Defer to `Data` for choosing an encoding.
979+
case deferredToData
980+
981+
/// Encode the `Data` as a Base64-encoded string. This is the default strategy.
982+
case base64
983+
984+
/// Encode the `Data` as a custom value encoded by the given closure.
985+
///
986+
/// If the closure fails to encode a value into the given encoder, the encoder will encode
987+
/// an empty object in its place.
988+
case custom((_ data: Data, _ encoder: Encoder) throws -> Void)
989+
}
956990
}

Tests/PMJSONTests/SwiftDecoderTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,38 @@ final class SwiftDecoderTests: XCTestCase {
432432
let date = try decoder.decode(Date.self, from: 541317080)
433433
XCTAssertEqual(date, Date(timeIntervalSinceReferenceDate: 541318080))
434434
}
435+
436+
// MARK: - DataDecodingStrategy
437+
438+
func testDecodeDataDefaultStrategy() throws {
439+
let input = "hello".data(using: .utf8)!
440+
let decoder = JSON.Decoder()
441+
// don't assume the default format since that's up to Data, just encode it first
442+
let json = try JSON.Encoder().encodeAsJSON(input)
443+
let data = try decoder.decode(Data.self, from: json)
444+
XCTAssertEqual(data, input)
445+
}
446+
447+
func testDecodeDataBase64() throws {
448+
var decoder = JSON.Decoder()
449+
decoder.dataDecodingStrategy = .base64
450+
let data = try decoder.decode(Data.self, from: "aGVsbG8=" as JSON)
451+
XCTAssertEqual(data, "hello".data(using: .utf8)!)
452+
}
453+
454+
func testDecodeDataCustom() throws {
455+
var decoder = JSON.Decoder()
456+
decoder.dataDecodingStrategy = .custom({ (decoder) -> Data in
457+
let container = try decoder.singleValueContainer()
458+
let str = try container.decode(String.self)
459+
guard let data = Data(base64Encoded: String(str.reversed())) else {
460+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Could not decode Data.")
461+
}
462+
return data
463+
})
464+
let data = try decoder.decode(Data.self, from: "=8GbsVGa" as JSON)
465+
XCTAssertEqual(data, "hello".data(using: .utf8)!)
466+
}
435467
}
436468

437469
private extension Date {

Tests/PMJSONTests/SwiftEncoderTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,43 @@ final class SwiftEncoderTests: XCTestCase {
811811
let json = try encoder.encodeAsJSON(Date())
812812
XCTAssertEqual(json, [:])
813813
}
814+
815+
// MARK: - DataEncodingStrategy
816+
817+
func testEncodeDataDefaultStrategy() throws {
818+
let input = "hello".data(using: .utf8)!
819+
let encoder = JSON.Encoder()
820+
// don't assume the default format since that's up to Data, just decode it after
821+
let json = try encoder.encodeAsJSON(input)
822+
let data = try JSON.Decoder().decode(Data.self, from: json)
823+
XCTAssertEqual(data, input)
824+
}
825+
826+
func testEncodeDataBase64() throws {
827+
var encoder = JSON.Encoder()
828+
encoder.dataEncodingStrategy = .base64
829+
let json = try encoder.encodeAsJSON("hello".data(using: .utf8)!)
830+
XCTAssertEqual(json, "aGVsbG8=")
831+
}
832+
833+
func testEncodeDataCustom() throws {
834+
var encoder = JSON.Encoder()
835+
encoder.dataEncodingStrategy = .custom({ (data, encoder) in
836+
let str = String(data.base64EncodedString().reversed())
837+
try str.encode(to: encoder)
838+
})
839+
let json = try encoder.encodeAsJSON("hello".data(using: .utf8)!)
840+
XCTAssertEqual(json, "=8GbsVGa")
841+
}
842+
843+
func testEncodeDataCustomNoEncode() throws {
844+
var encoder = JSON.Encoder()
845+
encoder.dataEncodingStrategy = .custom({ (data, encoder) in
846+
// do nothing
847+
})
848+
let json = try encoder.encodeAsJSON("hello".data(using: .utf8)!)
849+
XCTAssertEqual(json, [:])
850+
}
814851
}
815852

816853
private extension Date {

0 commit comments

Comments
 (0)