Say I have an object, Game
, which contains three properties, score
, firstName
, lastName
.
class Game: Encodable {
var firstName: String
var lastName: String
var score: Int
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case score
}
func encode(to encoder: Encoder) throws {
var encoder = encoder.container(keyedBy: CodingKeys.self)
try? encoder.encode(self.firstName, forKey: .firstName)
try? encoder.encode(self.lastName, forKey: .lastName)
try? encoder.encode(self.score, forKey: .score)
}
}
Say I want to persist this to disk, I can use JSONEncoder
let game = Game(firstName: "John", lastName: "Smith", score: 42)
let data = try JSONEncoder().encode(game)
{
firstName: "John",
lastName: "Smith",
score: 42
}
Now I want users to be able to generate the JSON on their own and share it to any app, but for privacy reasons I want to omit the last name, how could I create two implementations of encode(to encoder: Encoder)
for the one type?
Example Output:
{
firstName: "John",
score: 42
}
CodePudding user response:
You can add an additional variable to your class (make sure not to add this additional variable to CodingKeys enum) and use it in your encode(to encoder:
function as shown
class Game: Encodable { // assuming you have assigned initial values to all your properties or you have a init which initializes all these properties, I would have used struct but am not sure of your usecase so leaving it as is
var firstName: String
var lastName: String
var score: Int
var isLastNameEnabled: Bool
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case score
}
func encode(to encoder: Encoder) throws {
var encoder = encoder.container(keyedBy: CodingKeys.self)
try? encoder.encode(self.firstName, forKey: .firstName)
if isLastNameEnabled {
try? encoder.encode(self.lastName, forKey: .lastName)
}
try? encoder.encode(self.score, forKey: .score)
}
}
Finally, if you want to add last name as part of your JSON use
let game = Game(firstName: "John", lastName: "Smith", score: 42, isLastNameEnabled: true)
do {
let data = try JSONEncoder().encode(game)
let json = String(bytes: data, encoding: .utf8)
print(json)
}
catch {
print(error) //handle error here
}
O/P:
{ "firstName": "John", "score": 42, "lastName": "Smith" }
if you want to avoid last name use
let game = Game(firstName: "John", lastName: "Smith", score: 42, isLastNameEnabled: false)
do {
let data = try JSONEncoder().encode(game)
let json = String(bytes: data, encoding: .utf8)
print(json)
}
catch {
print(error) // handle error here
}
O/P:
{ "firstName":"John", "score":42 }
CodePudding user response:
There're some options. Besides Sandeep's solution with a Bool flag, polymorphism might be another one:
final class ShareableGame: Game {
init(_ game: Game) {
super.init(firstName: game.firstName,
lastName: game.lastName,
score: game.score)
}
override func encode(to encoder: Encoder) throws {
var encoder = encoder.container(keyedBy: CodingKeys.self)
try? encoder.encode(self.firstName, forKey: .firstName)
try? encoder.encode(self.score, forKey: .score)
}
}
let game = Game(firstName: "John", lastName: "Smith", score: 42)
let shareableGame = ShareableGame(game)
let shareableGameData = try JSONEncoder().encode(shareableGame)
print(String(bytes: shareableGameData, encoding: .utf8)!)
// {"firstName":"John","score":42}
(Assuming the Game class is defined similarly to this:)
class Game: Encodable {
let firstName: String
let lastName: String
let score: Int
init(firstName: String, lastName: String, score: Int) {
self.firstName = firstName
self.lastName = lastName
self.score = score
}
/*
encode(to:) throws can be omitted, since the default
implementation is exactly as expected.
*/
enum CodingKeys: String, CodingKey {
// Cannot be omitted, since it's used in subclasses.
case firstName
case lastName
case score
}
}
CodePudding user response:
You could make use of the userInfo
dictionary in the JSONEncoder
to set a flag whether this is a private encoding or not. This way you don't need to add properties to your type or create another one.
First we need a key for the dictionary, I have made a more general one here
let isPrivateInfoKey = CodingUserInfoKey(rawValue: "isPrivate")!
and then we need to check it in the encode function
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let isPrivate = encoder.userInfo[isPrivateInfoKey] as? Bool ?? true
try container.encode(self.firstName, forKey: .firstName)
if isPrivate {
try container.encode(self.lastName, forKey: .lastName)
}
try container.encode(self.score, forKey: .score)
}
Default is true if the key/value hasn't been added to the dictionary so it only needs to be set when the last name shouldn't be included
let encoder = JSONEncoder()
encoder.userInfo[isPrivateInfoKey] = false
do {
let game = Game(firstName: "John", lastName: "Smith", score: 42)
let data = try encoder.encode(game)
if let s = String(data: data, encoding: .utf8) { print(s) }
} catch {
print(error)
}