Skip to content

Commit ef9dffa

Browse files
authored
Merge pull request #11605 from itaiferber/top-level-codable-interception
Allow top-level Codable strategy/type interception
2 parents 85576fe + ee39ff7 commit ef9dffa

File tree

3 files changed

+99
-65
lines changed

3 files changed

+99
-65
lines changed

stdlib/public/SDK/Foundation/JSONEncoder.swift

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,11 @@ open class JSONEncoder {
129129
/// - throws: An error if any value throws an error during encoding.
130130
open func encode<T : Encodable>(_ value: T) throws -> Data {
131131
let encoder = _JSONEncoder(options: self.options)
132-
try value.encode(to: encoder)
133132

134-
guard encoder.storage.count > 0 else {
133+
guard let topLevel = try encoder.box_(value) else {
135134
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
136135
}
137136

138-
let topLevel = encoder.storage.popContainer()
139137
if topLevel is NSNull {
140138
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as null JSON fragment."))
141139
} else if topLevel is NSNumber {
@@ -468,77 +466,77 @@ extension _JSONEncoder : SingleValueEncodingContainer {
468466

469467
public func encode(_ value: Bool) throws {
470468
assertCanEncodeNewValue()
471-
self.storage.push(container: box(value))
469+
self.storage.push(container: self.box(value))
472470
}
473471

474472
public func encode(_ value: Int) throws {
475473
assertCanEncodeNewValue()
476-
self.storage.push(container: box(value))
474+
self.storage.push(container: self.box(value))
477475
}
478476

479477
public func encode(_ value: Int8) throws {
480478
assertCanEncodeNewValue()
481-
self.storage.push(container: box(value))
479+
self.storage.push(container: self.box(value))
482480
}
483481

484482
public func encode(_ value: Int16) throws {
485483
assertCanEncodeNewValue()
486-
self.storage.push(container: box(value))
484+
self.storage.push(container: self.box(value))
487485
}
488486

489487
public func encode(_ value: Int32) throws {
490488
assertCanEncodeNewValue()
491-
self.storage.push(container: box(value))
489+
self.storage.push(container: self.box(value))
492490
}
493491

494492
public func encode(_ value: Int64) throws {
495493
assertCanEncodeNewValue()
496-
self.storage.push(container: box(value))
494+
self.storage.push(container: self.box(value))
497495
}
498496

499497
public func encode(_ value: UInt) throws {
500498
assertCanEncodeNewValue()
501-
self.storage.push(container: box(value))
499+
self.storage.push(container: self.box(value))
502500
}
503501

504502
public func encode(_ value: UInt8) throws {
505503
assertCanEncodeNewValue()
506-
self.storage.push(container: box(value))
504+
self.storage.push(container: self.box(value))
507505
}
508506

509507
public func encode(_ value: UInt16) throws {
510508
assertCanEncodeNewValue()
511-
self.storage.push(container: box(value))
509+
self.storage.push(container: self.box(value))
512510
}
513511

514512
public func encode(_ value: UInt32) throws {
515513
assertCanEncodeNewValue()
516-
self.storage.push(container: box(value))
514+
self.storage.push(container: self.box(value))
517515
}
518516

519517
public func encode(_ value: UInt64) throws {
520518
assertCanEncodeNewValue()
521-
self.storage.push(container: box(value))
519+
self.storage.push(container: self.box(value))
522520
}
523521

524522
public func encode(_ value: String) throws {
525523
assertCanEncodeNewValue()
526-
self.storage.push(container: box(value))
524+
self.storage.push(container: self.box(value))
527525
}
528526

529527
public func encode(_ value: Float) throws {
530528
assertCanEncodeNewValue()
531-
try self.storage.push(container: box(value))
529+
try self.storage.push(container: self.box(value))
532530
}
533531

534532
public func encode(_ value: Double) throws {
535533
assertCanEncodeNewValue()
536-
try self.storage.push(container: box(value))
534+
try self.storage.push(container: self.box(value))
537535
}
538536

539537
public func encode<T : Encodable>(_ value: T) throws {
540538
assertCanEncodeNewValue()
541-
try self.storage.push(container: box(value))
539+
try self.storage.push(container: self.box(value))
542540
}
543541
}
544542

@@ -661,28 +659,32 @@ extension _JSONEncoder {
661659
}
662660

663661
fileprivate func box<T : Encodable>(_ value: T) throws -> NSObject {
664-
if T.self == Date.self {
662+
return try self.box_(value) ?? NSDictionary()
663+
}
664+
665+
// This method is called "box_" instead of "box" to disambiguate it from the overloads. Because the return type here is different from all of the "box" overloads (and is more general), any "box" calls in here would call back into "box" recursively instead of calling the appropriate overload, which is not what we want.
666+
fileprivate func box_<T : Encodable>(_ value: T) throws -> NSObject? {
667+
if T.self == Date.self || T.self == NSDate.self {
665668
// Respect Date encoding strategy
666669
return try self.box((value as! Date))
667-
} else if T.self == Data.self {
670+
} else if T.self == Data.self || T.self == NSData.self {
668671
// Respect Data encoding strategy
669672
return try self.box((value as! Data))
670-
} else if T.self == URL.self {
673+
} else if T.self == URL.self || T.self == NSURL.self {
671674
// Encode URLs as single strings.
672675
return self.box((value as! URL).absoluteString)
673-
} else if T.self == Decimal.self {
676+
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
674677
// JSONSerialization can natively handle NSDecimalNumber.
675-
return (value as! Decimal) as NSDecimalNumber
678+
return (value as! NSDecimalNumber)
676679
}
677680

678681
// The value should request a container from the _JSONEncoder.
679-
let topContainer = self.storage.containers.last
682+
let depth = self.storage.count
680683
try value.encode(to: self)
681684

682685
// The top container should be a new container.
683-
guard self.storage.containers.last! !== topContainer else {
684-
// If the value didn't request a container at all, encode the default container instead.
685-
return NSDictionary()
686+
guard self.storage.count > depth else {
687+
return nil
686688
}
687689

688690
return self.storage.popContainer()
@@ -865,7 +867,11 @@ open class JSONDecoder {
865867
}
866868

867869
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
868-
return try T(from: decoder)
870+
guard let value = try decoder.unbox(topLevel, as: T.self) else {
871+
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
872+
}
873+
874+
return value
869875
}
870876
}
871877

@@ -2088,13 +2094,13 @@ extension _JSONDecoder {
20882094

20892095
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
20902096
let decoded: T
2091-
if T.self == Date.self {
2097+
if T.self == Date.self || T.self == NSDate.self {
20922098
guard let date = try self.unbox(value, as: Date.self) else { return nil }
20932099
decoded = date as! T
2094-
} else if T.self == Data.self {
2100+
} else if T.self == Data.self || T.self == NSData.self {
20952101
guard let data = try self.unbox(value, as: Data.self) else { return nil }
20962102
decoded = data as! T
2097-
} else if T.self == URL.self {
2103+
} else if T.self == URL.self || T.self == NSURL.self {
20982104
guard let urlString = try self.unbox(value, as: String.self) else {
20992105
return nil
21002106
}
@@ -2105,7 +2111,7 @@ extension _JSONDecoder {
21052111
}
21062112

21072113
decoded = (url as! T)
2108-
} else if T.self == Decimal.self {
2114+
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
21092115
guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
21102116
decoded = decimal as! T
21112117
} else {

stdlib/public/SDK/Foundation/PlistEncoder.swift

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,12 @@ open class PropertyListEncoder {
8080
/// - throws: An error if any value throws an error during encoding.
8181
internal func encodeToTopLevelContainer<Value : Encodable>(_ value: Value) throws -> Any {
8282
let encoder = _PlistEncoder(options: self.options)
83-
try value.encode(to: encoder)
84-
85-
guard encoder.storage.count > 0 else {
83+
guard let topLevel = try encoder.box_(value) else {
8684
throw EncodingError.invalidValue(value,
8785
EncodingError.Context(codingPath: [],
8886
debugDescription: "Top-level \(Value.self) did not encode any values."))
8987
}
9088

91-
let topLevel = encoder.storage.popContainer()
9289
return topLevel
9390
}
9491
}
@@ -384,77 +381,77 @@ extension _PlistEncoder : SingleValueEncodingContainer {
384381

385382
public func encode(_ value: Bool) throws {
386383
assertCanEncodeNewValue()
387-
self.storage.push(container: box(value))
384+
self.storage.push(container: self.box(value))
388385
}
389386

390387
public func encode(_ value: Int) throws {
391388
assertCanEncodeNewValue()
392-
self.storage.push(container: box(value))
389+
self.storage.push(container: self.box(value))
393390
}
394391

395392
public func encode(_ value: Int8) throws {
396393
assertCanEncodeNewValue()
397-
self.storage.push(container: box(value))
394+
self.storage.push(container: self.box(value))
398395
}
399396

400397
public func encode(_ value: Int16) throws {
401398
assertCanEncodeNewValue()
402-
self.storage.push(container: box(value))
399+
self.storage.push(container: self.box(value))
403400
}
404401

405402
public func encode(_ value: Int32) throws {
406403
assertCanEncodeNewValue()
407-
self.storage.push(container: box(value))
404+
self.storage.push(container: self.box(value))
408405
}
409406

410407
public func encode(_ value: Int64) throws {
411408
assertCanEncodeNewValue()
412-
self.storage.push(container: box(value))
409+
self.storage.push(container: self.box(value))
413410
}
414411

415412
public func encode(_ value: UInt) throws {
416413
assertCanEncodeNewValue()
417-
self.storage.push(container: box(value))
414+
self.storage.push(container: self.box(value))
418415
}
419416

420417
public func encode(_ value: UInt8) throws {
421418
assertCanEncodeNewValue()
422-
self.storage.push(container: box(value))
419+
self.storage.push(container: self.box(value))
423420
}
424421

425422
public func encode(_ value: UInt16) throws {
426423
assertCanEncodeNewValue()
427-
self.storage.push(container: box(value))
424+
self.storage.push(container: self.box(value))
428425
}
429426

430427
public func encode(_ value: UInt32) throws {
431428
assertCanEncodeNewValue()
432-
self.storage.push(container: box(value))
429+
self.storage.push(container: self.box(value))
433430
}
434431

435432
public func encode(_ value: UInt64) throws {
436433
assertCanEncodeNewValue()
437-
self.storage.push(container: box(value))
434+
self.storage.push(container: self.box(value))
438435
}
439436

440437
public func encode(_ value: String) throws {
441438
assertCanEncodeNewValue()
442-
self.storage.push(container: box(value))
439+
self.storage.push(container: self.box(value))
443440
}
444441

445442
public func encode(_ value: Float) throws {
446443
assertCanEncodeNewValue()
447-
self.storage.push(container: box(value))
444+
self.storage.push(container: self.box(value))
448445
}
449446

450447
public func encode(_ value: Double) throws {
451448
assertCanEncodeNewValue()
452-
self.storage.push(container: box(value))
449+
self.storage.push(container: self.box(value))
453450
}
454451

455452
public func encode<T : Encodable>(_ value: T) throws {
456453
assertCanEncodeNewValue()
457-
try self.storage.push(container: box(value))
454+
try self.storage.push(container: self.box(value))
458455
}
459456
}
460457

@@ -477,25 +474,27 @@ extension _PlistEncoder {
477474
fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) }
478475
fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) }
479476
fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) }
480-
fileprivate func box(_ value: Data) -> NSObject { return NSData(data: value) }
481477

482478
fileprivate func box<T : Encodable>(_ value: T) throws -> NSObject {
483-
if T.self == Date.self {
484-
// PropertyListSerialization handles Date directly.
485-
return NSDate(timeIntervalSinceReferenceDate: (value as! Date).timeIntervalSinceReferenceDate)
486-
} else if T.self == Data.self {
487-
// PropertyListSerialization handles Data directly.
488-
return NSData(data: (value as! Data))
479+
return try self.box_(value) ?? NSDictionary()
480+
}
481+
482+
fileprivate func box_<T : Encodable>(_ value: T) throws -> NSObject? {
483+
if T.self == Date.self || T.self == NSDate.self {
484+
// PropertyListSerialization handles NSDate directly.
485+
return (value as! NSDate)
486+
} else if T.self == Data.self || T.self == NSData.self {
487+
// PropertyListSerialization handles NSData directly.
488+
return (value as! NSData)
489489
}
490490

491491
// The value should request a container from the _PlistEncoder.
492-
let currentTopContainer = self.storage.containers.last
492+
let depth = self.storage.count
493493
try value.encode(to: self)
494494

495495
// The top container should be a new container.
496-
guard self.storage.containers.last! !== currentTopContainer else {
497-
// If the value didn't request a container at all, encode the default container instead.
498-
return NSDictionary()
496+
guard self.storage.count > depth else {
497+
return nil
499498
}
500499

501500
return self.storage.popContainer()
@@ -644,7 +643,11 @@ open class PropertyListDecoder {
644643
/// - throws: An error if any value throws an error during decoding.
645644
internal func decode<T : Decodable>(_ type: T.Type, fromTopLevel container: Any) throws -> T {
646645
let decoder = _PlistDecoder(referencing: container, options: self.options)
647-
return try T(from: decoder)
646+
guard let value = try decoder.unbox(container, as: T.self) else {
647+
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
648+
}
649+
650+
return value
648651
}
649652
}
650653

@@ -1753,10 +1756,10 @@ extension _PlistDecoder {
17531756

17541757
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
17551758
let decoded: T
1756-
if T.self == Date.self {
1759+
if T.self == Date.self || T.self == NSDate.self {
17571760
guard let date = try self.unbox(value, as: Date.self) else { return nil }
17581761
decoded = date as! T
1759-
} else if T.self == Data.self {
1762+
} else if T.self == Data.self || T.self == NSData.self {
17601763
guard let data = try self.unbox(value, as: Data.self) else { return nil }
17611764
decoded = data as! T
17621765
} else {

0 commit comments

Comments
 (0)