Skip to content

Commit f3749b6

Browse files
committed
Initial draft of nil property change
1 parent 5860abd commit f3749b6

File tree

16 files changed

+107
-29
lines changed

16 files changed

+107
-29
lines changed

ParseSwift.playground/Pages/1 - Your first Object.xcplaygroundpage/Contents.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ struct GameScore: ParseObject {
3939

4040
//: Your own properties.
4141
var score: Int = 0
42+
43+
//: Optional custom properties need to be marked with @NullableProperty or setting properties to `nil` won't propagate to server
44+
@NullableProperty var gameEndDate: Date?
4245

4346
/*:
4447
It's recommended the developer adds the emptyObject computed property or similar.
@@ -76,10 +79,10 @@ struct GameData: ParseObject {
7679
var ACL: ParseACL?
7780

7881
//: Your own properties.
79-
var polygon: ParsePolygon?
82+
@NullableProperty var polygon: ParsePolygon?
8083
//: `ParseBytes` needs to be a part of the original schema
8184
//: or else you will need your masterKey to force an upgrade.
82-
var bytes: ParseBytes?
85+
@NullableProperty var bytes: ParseBytes?
8386
}
8487

8588
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ struct GameScore: ParseObject {
1919

2020
//: Your own properties.
2121
var score: Int = 0
22-
var location: ParseGeoPoint?
23-
var name: String?
22+
@NullableProperty var location: ParseGeoPoint?
23+
@NullableProperty var name: String?
2424
}
2525

2626
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/12 - Roles and Relations.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct User: ParseUser {
2828
var authData: [String: [String: String]?]?
2929

3030
//: Your custom keys.
31-
var customKey: String?
31+
@NullableProperty var customKey: String?
3232
}
3333

3434
struct Role<RoleUser: ParseUser>: ParseRole {

ParseSwift.playground/Pages/14 - Config.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ struct Config: ParseConfig {
1818

1919
//: If your server Config has any parameters their names and types should
2020
//: match your ParseCondig properties:
21-
var welcomeMessage: String?
22-
var winningNumber: Int?
21+
@NullableProperty var welcomeMessage: String?
22+
@NullableProperty var winningNumber: Int?
2323
}
2424

2525
/*: Go to your Parse Dashboard and click `Config->Create a parameter`:

ParseSwift.playground/Pages/17 - SwiftUI - Finding Objects.xcplaygroundpage/Contents.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ struct GameScore: ParseObject {
2828

2929
//: Your own properties.
3030
var score: Int = 0
31-
var location: ParseGeoPoint?
32-
var name: String?
33-
var myFiles: [ParseFile]?
31+
@NullableProperty var location: ParseGeoPoint?
32+
@NullableProperty var name: String?
33+
@NullableProperty var myFiles: [ParseFile]?
3434
}
3535

3636
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/18 - SwiftUI - Finding Objects With Custom ViewModel.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ struct GameScore: ParseObject {
2929

3030
//: Your own properties.
3131
var score: Int = 0
32-
var location: ParseGeoPoint?
33-
var name: String?
32+
@NullableProperty var location: ParseGeoPoint?
33+
@NullableProperty var name: String?
3434
}
3535

3636
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/19 - SwiftUI - LiveQuery.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ struct GameScore: ParseObject {
2727

2828
//: Your own properties.
2929
var score: Int = 0
30-
var location: ParseGeoPoint?
31-
var name: String?
30+
@NullableProperty var location: ParseGeoPoint?
31+
@NullableProperty var name: String?
3232
}
3333

3434
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ struct GameScore: ParseObject {
2020
var ACL: ParseACL?
2121

2222
//: Your own properties.
23-
var score: Int?
24-
var timeStamp: Date? = Date()
25-
var oldScore: Int?
23+
@NullableProperty var score: Int?
24+
@NullableProperty var timeStamp: Date? = Date()
25+
@NullableProperty var oldScore: Int?
2626
}
2727

2828
var score = GameScore()

ParseSwift.playground/Pages/3 - User - Sign Up.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct User: ParseUser {
2828
var authData: [String: [String: String]?]?
2929

3030
//: Your custom keys.
31-
var customKey: String?
31+
@NullableProperty var customKey: String?
3232
}
3333

3434
/*: Sign up user asynchronously - Performs work on background

ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ struct User: ParseUser {
2828
var authData: [String: [String: String]?]?
2929

3030
//: Your custom keys.
31-
var customKey: String?
32-
var score: GameScore?
33-
var targetScore: GameScore?
34-
var allScores: [GameScore]?
31+
@NullableProperty var customKey: String?
32+
@NullableProperty var score: GameScore?
33+
@NullableProperty var targetScore: GameScore?
34+
@NullableProperty var allScores: [GameScore]?
3535

3636
/*:
3737
It's recommended the developer adds the emptyObject computed property or similar.
@@ -68,7 +68,7 @@ struct GameScore: ParseObject {
6868
var ACL: ParseACL?
6969

7070
//: Your own properties.
71-
var score: Int? = 0
71+
@NullableProperty var score: Int? = 0
7272
}
7373

7474
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ struct Installation: ParseInstallation {
3434
var localeIdentifier: String?
3535

3636
//: Your custom keys
37-
var customKey: String?
37+
@NullableProperty var customKey: String?
3838

3939
/*:
4040
It's recommended the developer adds the emptyObject computed property or similar.

ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct GameScore: ParseObject {
2323
var location: ParseGeoPoint?
2424

2525
//: Your own properties
26-
var score: Int?
26+
@NullableProperty var score: Int?
2727
}
2828

2929
//: It's recommended to place custom initializers in an extension

ParseSwift.playground/Pages/8 - Pointers.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct Book: ParseObject {
2323
var relatedBook: Pointer<Book>?
2424

2525
//: Your own properties.
26-
var title: String?
26+
@NullableProperty var title: String?
2727
}
2828

2929
//: It's recommended to place custom initializers in an extension
@@ -45,7 +45,7 @@ struct Author: ParseObject {
4545
//: Your own properties.
4646
var name: String
4747
var book: Book
48-
var otherBooks: [Book]?
48+
@NullableProperty var otherBooks: [Book]?
4949

5050
init() {
5151
self.name = "hello"

ParseSwift.playground/Pages/9 - Files.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ struct GameScore: ParseObject {
2323

2424
//: Your own properties.
2525
var score: Int = 0
26-
var profilePicture: ParseFile?
27-
var myData: ParseFile?
26+
@NullableProperty var profilePicture: ParseFile?
27+
@NullableProperty var myData: ParseFile?
2828
}
2929

3030
//: It's recommended to place custom initializers in an extension

ParseSwift.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,10 @@
587587
91F346C3269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */; };
588588
91F346C4269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */; };
589589
91F346C5269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */; };
590+
98A7D7BC272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
591+
98A7D7BD272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
592+
98A7D7BE272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
593+
98A7D7BF272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
590594
F971F4F624DE381A006CB79B /* ParseEncoderExtraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F971F4F524DE381A006CB79B /* ParseEncoderExtraTests.swift */; };
591595
F97B45CE24D9C6F200F4A88B /* ParseCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97B45B424D9C6F200F4A88B /* ParseCoding.swift */; };
592596
F97B45CF24D9C6F200F4A88B /* ParseCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97B45B424D9C6F200F4A88B /* ParseCoding.swift */; };
@@ -961,6 +965,7 @@
961965
91F346B8269B766C005727B6 /* CloudViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudViewModel.swift; sourceTree = "<group>"; };
962966
91F346BD269B77B5005727B6 /* CloudObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudObservable.swift; sourceTree = "<group>"; };
963967
91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloudViewModelTests.swift; sourceTree = "<group>"; };
968+
98A7D7BB272AF07300F5F190 /* NullableProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullableProperty.swift; sourceTree = "<group>"; };
964969
F971F4F524DE381A006CB79B /* ParseEncoderExtraTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseEncoderExtraTests.swift; sourceTree = "<group>"; };
965970
F97B45B424D9C6F200F4A88B /* ParseCoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseCoding.swift; sourceTree = "<group>"; };
966971
F97B45B524D9C6F200F4A88B /* AnyDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = "<group>"; };
@@ -1480,6 +1485,7 @@
14801485
F97B45B824D9C6F200F4A88B /* AnyCodable.swift */,
14811486
F97B45B524D9C6F200F4A88B /* AnyDecodable.swift */,
14821487
F97B45B924D9C6F200F4A88B /* AnyEncodable.swift */,
1488+
98A7D7BB272AF07300F5F190 /* NullableProperty.swift */,
14831489
F97B45B724D9C6F200F4A88B /* Extensions.swift */,
14841490
F97B45B424D9C6F200F4A88B /* ParseCoding.swift */,
14851491
F97B45B624D9C6F200F4A88B /* ParseEncoder.swift */,
@@ -2089,6 +2095,7 @@
20892095
70C5509225B4A99100B5DBC2 /* AddRelation.swift in Sources */,
20902096
708D035225215F9B00646C70 /* Deletable.swift in Sources */,
20912097
F97B466424D9C88600F4A88B /* SecureStorage.swift in Sources */,
2098+
98A7D7BC272AF07300F5F190 /* NullableProperty.swift in Sources */,
20922099
7004C22025B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
20932100
7003959525A10DFC0052CB31 /* Messages.swift in Sources */,
20942101
703B091126BD992E005A112F /* ParseOperation+async.swift in Sources */,
@@ -2298,6 +2305,7 @@
22982305
70C5509325B4A99100B5DBC2 /* AddRelation.swift in Sources */,
22992306
708D035325215F9B00646C70 /* Deletable.swift in Sources */,
23002307
F97B466524D9C88600F4A88B /* SecureStorage.swift in Sources */,
2308+
98A7D7BD272AF07300F5F190 /* NullableProperty.swift in Sources */,
23012309
7004C22125B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
23022310
7003959625A10DFC0052CB31 /* Messages.swift in Sources */,
23032311
703B091226BD992E005A112F /* ParseOperation+async.swift in Sources */,
@@ -2602,6 +2610,7 @@
26022610
70C5509525B4A99100B5DBC2 /* AddRelation.swift in Sources */,
26032611
708D035525215F9B00646C70 /* Deletable.swift in Sources */,
26042612
70110D55250680140091CC1D /* ParseConstants.swift in Sources */,
2613+
98A7D7BF272AF07300F5F190 /* NullableProperty.swift in Sources */,
26052614
7004C22325B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
26062615
7003959825A10DFC0052CB31 /* Messages.swift in Sources */,
26072616
703B091426BD992E005A112F /* ParseOperation+async.swift in Sources */,
@@ -2725,6 +2734,7 @@
27252734
70C5509425B4A99100B5DBC2 /* AddRelation.swift in Sources */,
27262735
708D035425215F9B00646C70 /* Deletable.swift in Sources */,
27272736
70110D54250680140091CC1D /* ParseConstants.swift in Sources */,
2737+
98A7D7BE272AF07300F5F190 /* NullableProperty.swift in Sources */,
27282738
7004C22225B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
27292739
7003959725A10DFC0052CB31 /* Messages.swift in Sources */,
27302740
703B091326BD992E005A112F /* ParseOperation+async.swift in Sources */,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// NullableProperty.swift
3+
//
4+
// Created by Steven Grosmark on 6/10/20.
5+
// Source: https://github.com/g-mark/NullCodable
6+
//
7+
8+
import Foundation
9+
10+
/// Property wrapper that encodes `nil` optional values as a delete operation
11+
/// when encoded using `JSONEncoder`.
12+
///
13+
/// For example, adding `@NullableProperty` like this:
14+
/// ```swift
15+
/// struct Test: Codable {
16+
/// @NullableProperty var name: String? = nil
17+
/// }
18+
/// ```
19+
///
20+
@propertyWrapper
21+
public struct NullableProperty<Wrapped> {
22+
23+
public var wrappedValue: Wrapped?
24+
25+
public init(wrappedValue: Wrapped?) {
26+
self.wrappedValue = wrappedValue
27+
}
28+
}
29+
30+
extension NullableProperty: Encodable where Wrapped: Encodable {
31+
32+
public func encode(to encoder: Encoder) throws {
33+
var container = encoder.singleValueContainer()
34+
switch wrappedValue {
35+
case .some(let value):
36+
// Standard value
37+
try container.encode(value)
38+
39+
case .none:
40+
// Empty value, encode delete op command
41+
try container.encode(Delete())
42+
}
43+
}
44+
}
45+
46+
extension NullableProperty: Decodable where Wrapped: Decodable {
47+
48+
public init(from decoder: Decoder) throws {
49+
let container = try decoder.singleValueContainer()
50+
// Values coming from server won't have the delete operation, so we can just check if it's a null value and then decode normally.
51+
if !container.decodeNil() {
52+
wrappedValue = try container.decode(Wrapped.self)
53+
}
54+
}
55+
}
56+
57+
extension NullableProperty: Hashable where Wrapped: Hashable { }
58+
extension NullableProperty: Equatable where Wrapped: Equatable { }
59+
60+
extension KeyedDecodingContainer {
61+
62+
public func decode<Wrapped>(_ type: NullableProperty<Wrapped>.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> NullableProperty<Wrapped> where Wrapped: Decodable {
63+
return try decodeIfPresent(NullableProperty<Wrapped>.self, forKey: key) ?? NullableProperty<Wrapped>(wrappedValue: nil)
64+
}
65+
}

0 commit comments

Comments
 (0)