Skip to content

Commit 90bed04

Browse files
committed
Complex objects need to be combined after creating a nested container.
1 parent 9d9de32 commit 90bed04

File tree

2 files changed

+264
-3
lines changed

2 files changed

+264
-3
lines changed

Sources/Coding/Encoding.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ public extension Encoding {
2020
///
2121
static func combine(_ encodings: Self...) -> Self {
2222
.init { value, encoder in
23+
var container = encoder.unkeyedContainer()
24+
for encoding in encodings {
25+
try encoding.encode(value, container.superEncoder())
26+
}
27+
}
28+
}
29+
30+
static func combineWithKeys<Key: CodingKey>(_ keyType: Key.Type, _ encodings: Self...) -> Self {
31+
.init { value, encoder in
32+
_ = encoder.container(keyedBy: keyType)
2333
for encoding in encodings {
2434
try encoding.encode(value, encoder)
2535
}
@@ -65,6 +75,14 @@ public extension Encoding {
6575
try self.encode(value ?? replacementValue, encoder)
6676
}
6777
}
78+
79+
/// Turns an encoding of a value into one that encodes that value nested inside a keyed container with the given key.
80+
func withKey<Key: CodingKey>(_ key: Key) -> Self {
81+
.init { value, encoder in
82+
var container = encoder.container(keyedBy: Key.self)
83+
try self.encode(value, container.superEncoder(forKey: key))
84+
}
85+
}
6886
}
6987

7088
// MARK: - Encoder API
@@ -115,7 +133,7 @@ public extension Encoding where Value: Encodable {
115133
}
116134
}
117135

118-
public extension Encoding where Value: Sequence, Value.Element: Encodable {
136+
public extension Encoding where Value: Sequence {
119137
static func arrayOf(_ encoding: Encoding<Value.Element>) -> Self {
120138
.init { value, encoder in
121139
var container = encoder.unkeyedContainer()

Tests/CodingTests/EncodingTests.swift

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ final class EncodingTests: XCTestCase {
6161
stringValue(
6262
try encoder.encode(
6363
Value(a: 123, b: nil),
64-
as: .combine(
64+
as: .combineWithKeys(
65+
Value.CodingKeys.self,
66+
6567
Encoding<Int>
6668
.withKey(Value.CodingKeys.a)
6769
.pullback(\.a),
@@ -92,7 +94,9 @@ final class EncodingTests: XCTestCase {
9294
stringValue(
9395
try encoder.encode(
9496
Value(a: 123, b: nil),
95-
as: .combine(
97+
as: .combineWithKeys(
98+
Value.CodingKeys.self,
99+
96100
Encoding<Int>
97101
.withKey(Value.CodingKeys.a)
98102
.pullback(\.a),
@@ -381,7 +385,246 @@ final class EncodingTests: XCTestCase {
381385
)
382386
}
383387

388+
// MARK: - Complex Encoding
389+
390+
func testNestedEncoding() {
391+
encoder.outputFormatting = .prettyPrinted
392+
393+
let car = Car(
394+
name: "McLaren F1",
395+
model: .init(name: "F1"),
396+
manufacturer: .init(name: "McLaren"),
397+
availableColors: ["red", "silver"]
398+
)
399+
400+
XCTAssertEqual(
401+
"""
402+
{
403+
"manufacturer" : {
404+
"name" : "McLaren"
405+
},
406+
"model" : {
407+
"name" : "F1",
408+
"engineSizes" : [
409+
5000,
410+
6000,
411+
7000
412+
]
413+
},
414+
"availableColors" : [
415+
"RED",
416+
"SILVER"
417+
],
418+
"name" : "McLaren F1"
419+
}
420+
""",
421+
stringValue(try encoder.encode(car, as: .default))
422+
)
423+
}
424+
425+
func testCollectionOfComplexTypes() {
426+
encoder.outputFormatting = .prettyPrinted
427+
428+
let carOne = Car(
429+
name: "McLaren F1",
430+
model: .init(name: "F1"),
431+
manufacturer: .init(name: "McLaren"),
432+
availableColors: ["red", "silver"]
433+
)
434+
435+
let carTwo = Car(
436+
name: "Porsche 911",
437+
model: .init(name: "911"),
438+
manufacturer: .init(name: "Porsche"),
439+
availableColors: ["red", "yellow"]
440+
)
441+
442+
XCTAssertEqual(
443+
"""
444+
[
445+
{
446+
"manufacturer" : {
447+
"name" : "McLaren"
448+
},
449+
"model" : {
450+
"name" : "F1",
451+
"engineSizes" : [
452+
5000,
453+
6000,
454+
7000
455+
]
456+
},
457+
"availableColors" : [
458+
"RED",
459+
"SILVER"
460+
],
461+
"name" : "McLaren F1"
462+
},
463+
{
464+
"manufacturer" : {
465+
"name" : "Porsche"
466+
},
467+
"model" : {
468+
"name" : "911",
469+
"engineSizes" : [
470+
5000,
471+
6000,
472+
7000
473+
]
474+
},
475+
"availableColors" : [
476+
"RED",
477+
"YELLOW"
478+
],
479+
"name" : "Porsche 911"
480+
}
481+
]
482+
""",
483+
stringValue(try encoder.encode([carOne, carTwo], as: .arrayOf(.default)))
484+
)
485+
}
486+
487+
func testUnkeyedEncodingOfValueAttributes() {
488+
struct Value {
489+
var a: Int
490+
var b: String
491+
var c: Bool
492+
}
493+
494+
let value = Value(a: 1, b: "A", c: true)
495+
496+
let encoding: Encoding<Value> = .combine(
497+
Encoding<Int>.singleValue.pullback(\.a),
498+
Encoding<String>.singleValue.pullback(\.b),
499+
Encoding<Bool>.singleValue.pullback(\.c)
500+
)
501+
502+
XCTAssertEqual(
503+
"""
504+
[1,"A",true]
505+
""",
506+
stringValue(try encoder.encode(value, as: encoding))
507+
)
508+
}
509+
510+
func testEncodingOfValueAttributesAsArrayOfIndividualObjects() {
511+
struct Value {
512+
var a: Int
513+
var b: String
514+
var c: Bool
515+
}
516+
517+
enum ValueKeys: CodingKey {
518+
case a, b, c
519+
}
520+
521+
let value = Value(a: 1, b: "A", c: true)
522+
523+
let encoding: Encoding<Value> = .combine(
524+
Encoding<Int>.withKey(ValueKeys.a).pullback(\.a),
525+
Encoding<String>.withKey(ValueKeys.b).pullback(\.b),
526+
Encoding<Bool>.withKey(ValueKeys.c).pullback(\.c)
527+
)
528+
529+
XCTAssertEqual(
530+
"""
531+
[{"a":1},{"b":"A"},{"c":true}]
532+
""",
533+
stringValue(try encoder.encode(value, as: encoding))
534+
)
535+
}
536+
384537
private func stringValue(_ data: Data) -> String {
385538
String(data: data, encoding: .utf8)!
386539
}
387540
}
541+
542+
// MARK: - Test encodings
543+
544+
struct Car {
545+
var name: String
546+
var model: Model
547+
var manufacturer: Manufacturer
548+
var availableColors: [String]
549+
550+
enum CodingKeys: CodingKey {
551+
case name
552+
case model
553+
case manufacturer
554+
case availableColors
555+
}
556+
}
557+
558+
struct Model {
559+
var name: String
560+
var engineSizes = [5000, 6000, 7000]
561+
562+
enum CodingKeys: CodingKey {
563+
case name
564+
case engineSizes
565+
}
566+
}
567+
568+
struct Manufacturer {
569+
var name: String
570+
571+
enum CodingKeys: CodingKey {
572+
case name
573+
}
574+
}
575+
576+
extension Encoding where Value == Manufacturer {
577+
static let name: Self = Encoding<String>
578+
.withKey(Model.CodingKeys.name)
579+
.pullback(\.name)
580+
581+
static let `default`: Self = .combineWithKeys(
582+
Manufacturer.CodingKeys.self,
583+
name
584+
)
585+
}
586+
587+
extension Encoding where Value == Model {
588+
static let name: Self = Encoding<String>
589+
.withKey(Model.CodingKeys.name)
590+
.pullback(\.name)
591+
592+
static let engineSizes: Self = Encoding<[Int]>
593+
.withKey(Model.CodingKeys.engineSizes)
594+
.pullback(\.engineSizes)
595+
596+
static let `default`: Self = .combineWithKeys(
597+
Model.CodingKeys.self,
598+
name,
599+
engineSizes
600+
)
601+
}
602+
603+
extension Encoding where Value == Car {
604+
static let name: Self = Encoding<String>
605+
.withKey(Car.CodingKeys.name)
606+
.pullback(\.name)
607+
608+
static let model: Self = Encoding<Model>
609+
.default
610+
.withKey(Car.CodingKeys.model)
611+
.pullback(\.model)
612+
613+
static let manufacturer: Self = Encoding<Manufacturer>
614+
.default
615+
.withKey(Car.CodingKeys.manufacturer)
616+
.pullback(\.manufacturer)
617+
618+
static let colors: Self = Encoding<[String]>
619+
.arrayOf(.singleValue.pullback { $0.uppercased() })
620+
.withKey(Car.CodingKeys.availableColors)
621+
.pullback(\.availableColors)
622+
623+
static let `default`: Self = .combineWithKeys(
624+
Car.CodingKeys.self,
625+
name,
626+
model,
627+
manufacturer,
628+
colors
629+
)
630+
}

0 commit comments

Comments
 (0)