Skip to content

Setting property to nil causes delete op on save #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ struct GameScore: ParseObject {
//: Your own properties.
var score: Int = 0

//: Optional custom properties need to be marked with @NullableProperty or
// setting properties to `nil` won't propagate to server
@NullableProperty var gameEndDate: Date?
Copy link
Contributor

@cbaker6 cbaker6 Oct 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For playgrounds this probably only need a couple of examples. You can add a separate page that shows multiple different use-cases.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I wanted to make sure to cover any potential cases in case the user was only looking a one page of the Playground instead of looking through some of the earlier pages. I can change this to only add the changes to the first section, with maybe a little extra info there. I don't think there are really any other use-cases, if you're going to set a property to nil at some point then you'd need the wrapper.


/*:
It's recommended the developer adds the emptyObject computed property or similar.
Gets an empty version of the respective object. This can be used when you only need to update a
Expand Down Expand Up @@ -76,10 +80,10 @@ struct GameData: ParseObject {
var ACL: ParseACL?

//: Your own properties.
var polygon: ParsePolygon?
@NullableProperty var polygon: ParsePolygon?
//: `ParseBytes` needs to be a part of the original schema
//: or else you will need your masterKey to force an upgrade.
var bytes: ParseBytes?
@NullableProperty var bytes: ParseBytes?
}

//: It's recommended to place custom initializers in an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ struct GameScore: ParseObject {

//: Your own properties.
var score: Int = 0
var location: ParseGeoPoint?
var name: String?
@NullableProperty var location: ParseGeoPoint?
@NullableProperty var name: String?
}

//: It's recommended to place custom initializers in an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct User: ParseUser {
var authData: [String: [String: String]?]?

//: Your custom keys.
var customKey: String?
@NullableProperty var customKey: String?
}

struct Role<RoleUser: ParseUser>: ParseRole {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ struct Config: ParseConfig {

//: If your server Config has any parameters their names and types should
//: match your ParseCondig properties:
var welcomeMessage: String?
var winningNumber: Int?
@NullableProperty var welcomeMessage: String?
@NullableProperty var winningNumber: Int?
}

/*: Go to your Parse Dashboard and click `Config->Create a parameter`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ struct GameScore: ParseObject {

//: Your own properties.
var score: Int = 0
var location: ParseGeoPoint?
var name: String?
var myFiles: [ParseFile]?
@NullableProperty var location: ParseGeoPoint?
@NullableProperty var name: String?
@NullableProperty var myFiles: [ParseFile]?
}

//: It's recommended to place custom initializers in an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ struct GameScore: ParseObject {

//: Your own properties.
var score: Int = 0
var location: ParseGeoPoint?
var name: String?
@NullableProperty var location: ParseGeoPoint?
@NullableProperty var name: String?
}

//: It's recommended to place custom initializers in an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ struct GameScore: ParseObject {

//: Your own properties.
var score: Int = 0
var location: ParseGeoPoint?
var name: String?
@NullableProperty var location: ParseGeoPoint?
@NullableProperty var name: String?
}

//: It's recommended to place custom initializers in an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ struct GameScore: ParseObject {
var ACL: ParseACL?

//: Your own properties.
var score: Int?
var timeStamp: Date? = Date()
var oldScore: Int?
@NullableProperty var score: Int?
@NullableProperty var timeStamp: Date? = Date()
@NullableProperty var oldScore: Int?
}

var score = GameScore()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct User: ParseUser {
var authData: [String: [String: String]?]?

//: Your custom keys.
var customKey: String?
@NullableProperty var customKey: String?
}

/*: Sign up user asynchronously - Performs work on background
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ struct User: ParseUser {
var authData: [String: [String: String]?]?

//: Your custom keys.
var customKey: String?
var score: GameScore?
var targetScore: GameScore?
var allScores: [GameScore]?
@NullableProperty var customKey: String?
@NullableProperty var score: GameScore?
@NullableProperty var targetScore: GameScore?
@NullableProperty var allScores: [GameScore]?

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

//: Your own properties.
var score: Int? = 0
@NullableProperty var score: Int? = 0
}

//: It's recommended to place custom initializers in an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct Installation: ParseInstallation {
var localeIdentifier: String?

//: Your custom keys
var customKey: String?
@NullableProperty var customKey: String?

/*:
It's recommended the developer adds the emptyObject computed property or similar.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct GameScore: ParseObject {
var location: ParseGeoPoint?

//: Your own properties
var score: Int?
@NullableProperty var score: Int?
}

//: It's recommended to place custom initializers in an extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct Book: ParseObject {
var relatedBook: Pointer<Book>?

//: Your own properties.
var title: String?
@NullableProperty var title: String?
}

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

init() {
self.name = "hello"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ struct GameScore: ParseObject {

//: Your own properties.
var score: Int = 0
var profilePicture: ParseFile?
var myData: ParseFile?
@NullableProperty var profilePicture: ParseFile?
@NullableProperty var myData: ParseFile?
}

//: It's recommended to place custom initializers in an extension
Expand Down
10 changes: 10 additions & 0 deletions ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,10 @@
91F346C3269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */; };
91F346C4269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */; };
91F346C5269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */; };
98A7D7BC272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
98A7D7BD272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
98A7D7BE272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
98A7D7BF272AF07300F5F190 /* NullableProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7D7BB272AF07300F5F190 /* NullableProperty.swift */; };
F971F4F624DE381A006CB79B /* ParseEncoderExtraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F971F4F524DE381A006CB79B /* ParseEncoderExtraTests.swift */; };
F97B45CE24D9C6F200F4A88B /* ParseCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97B45B424D9C6F200F4A88B /* ParseCoding.swift */; };
F97B45CF24D9C6F200F4A88B /* ParseCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97B45B424D9C6F200F4A88B /* ParseCoding.swift */; };
Expand Down Expand Up @@ -961,6 +965,7 @@
91F346B8269B766C005727B6 /* CloudViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudViewModel.swift; sourceTree = "<group>"; };
91F346BD269B77B5005727B6 /* CloudObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudObservable.swift; sourceTree = "<group>"; };
91F346C2269B88F7005727B6 /* ParseCloudViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloudViewModelTests.swift; sourceTree = "<group>"; };
98A7D7BB272AF07300F5F190 /* NullableProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullableProperty.swift; sourceTree = "<group>"; };
F971F4F524DE381A006CB79B /* ParseEncoderExtraTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseEncoderExtraTests.swift; sourceTree = "<group>"; };
F97B45B424D9C6F200F4A88B /* ParseCoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseCoding.swift; sourceTree = "<group>"; };
F97B45B524D9C6F200F4A88B /* AnyDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1480,6 +1485,7 @@
F97B45B824D9C6F200F4A88B /* AnyCodable.swift */,
F97B45B524D9C6F200F4A88B /* AnyDecodable.swift */,
F97B45B924D9C6F200F4A88B /* AnyEncodable.swift */,
98A7D7BB272AF07300F5F190 /* NullableProperty.swift */,
F97B45B724D9C6F200F4A88B /* Extensions.swift */,
F97B45B424D9C6F200F4A88B /* ParseCoding.swift */,
F97B45B624D9C6F200F4A88B /* ParseEncoder.swift */,
Expand Down Expand Up @@ -2089,6 +2095,7 @@
70C5509225B4A99100B5DBC2 /* AddRelation.swift in Sources */,
708D035225215F9B00646C70 /* Deletable.swift in Sources */,
F97B466424D9C88600F4A88B /* SecureStorage.swift in Sources */,
98A7D7BC272AF07300F5F190 /* NullableProperty.swift in Sources */,
7004C22025B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
7003959525A10DFC0052CB31 /* Messages.swift in Sources */,
703B091126BD992E005A112F /* ParseOperation+async.swift in Sources */,
Expand Down Expand Up @@ -2298,6 +2305,7 @@
70C5509325B4A99100B5DBC2 /* AddRelation.swift in Sources */,
708D035325215F9B00646C70 /* Deletable.swift in Sources */,
F97B466524D9C88600F4A88B /* SecureStorage.swift in Sources */,
98A7D7BD272AF07300F5F190 /* NullableProperty.swift in Sources */,
7004C22125B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
7003959625A10DFC0052CB31 /* Messages.swift in Sources */,
703B091226BD992E005A112F /* ParseOperation+async.swift in Sources */,
Expand Down Expand Up @@ -2602,6 +2610,7 @@
70C5509525B4A99100B5DBC2 /* AddRelation.swift in Sources */,
708D035525215F9B00646C70 /* Deletable.swift in Sources */,
70110D55250680140091CC1D /* ParseConstants.swift in Sources */,
98A7D7BF272AF07300F5F190 /* NullableProperty.swift in Sources */,
7004C22325B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
7003959825A10DFC0052CB31 /* Messages.swift in Sources */,
703B091426BD992E005A112F /* ParseOperation+async.swift in Sources */,
Expand Down Expand Up @@ -2725,6 +2734,7 @@
70C5509425B4A99100B5DBC2 /* AddRelation.swift in Sources */,
708D035425215F9B00646C70 /* Deletable.swift in Sources */,
70110D54250680140091CC1D /* ParseConstants.swift in Sources */,
98A7D7BE272AF07300F5F190 /* NullableProperty.swift in Sources */,
7004C22225B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
7003959725A10DFC0052CB31 /* Messages.swift in Sources */,
703B091326BD992E005A112F /* ParseOperation+async.swift in Sources */,
Expand Down
64 changes: 64 additions & 0 deletions Sources/ParseSwift/Coding/NullableProperty.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// NullableProperty.swift
//
// Created by Steven Grosmark on 6/10/20.
// Source: https://github.com/g-mark/NullCodable
//

import Foundation

/// Property wrapper that encodes `nil` optional values as a delete operation
/// when encoded using `JSONEncoder`.
///
/// For example, adding `@NullableProperty` like this:
/// ```swift
/// struct Test: Codable {
/// @NullableProperty var name: String? = nil
/// }
/// ```
///
@propertyWrapper
public struct NullableProperty<Wrapped> {

public var wrappedValue: Wrapped?

public init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
}
}

extension NullableProperty: Encodable where Wrapped: Encodable {

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch wrappedValue {
case .some(let value):
try container.encode(value)

case .none:
try container.encode(Delete())
}
}
}

extension NullableProperty: Decodable where Wrapped: Decodable {

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
wrappedValue = try container.decode(Wrapped.self)
}
}
}

extension NullableProperty: Hashable where Wrapped: Hashable { }
extension NullableProperty: Equatable where Wrapped: Equatable { }

extension KeyedDecodingContainer {

public func decode<Wrapped>(_ type: NullableProperty<Wrapped>.Type, forKey key: KeyedDecodingContainer<K>.Key)
throws -> NullableProperty<Wrapped> where Wrapped: Decodable {
return try decodeIfPresent(NullableProperty<Wrapped>.self, forKey: key) ??
NullableProperty<Wrapped>(wrappedValue: nil)
}
}