'How to filter sensitive data server-side for object based on scenario

Scenario:

  • Class Example, subclassed from PFObject. Some sensitive properties are optional/can be nil.
  • User A creates examples locally on the client and saves it directly to the server (with ACL set to this user only)
  • User B should only see a subset of this data.

My approach:

  • create a cloud code function getExample for User B to call
  • getExample queries those examples, deletes all sensitive properties on the results (or recreates the object with only allowed properties) and returns those filtered objects.

Problem:

  • Whats the best approach to tell the parse engine that the return value is actually of type Example and make it parse it automatically? (call to server is via PFCloud.callFunction(inBackground: ...))?

Thank you (also, any architecture advise highly appreciated).



Solution 1:[1]

Decoding backend JSON responses in Swift is almost trivial nowadays, with the Decodable protocol and the JSONDecoder class from the Foundation library.

Here's an example with an optional field:

import Foundation

let jsonString1 = """
{
    "stringField" : "stringValue",
    "intField" : 1,
    "floatField" : 1.1
}
"""

let jsonString2 = """
{
    "stringField" : "stringValue",
    "intField" : 1,
    "floatField" : 1.1,
    "optionalField" : "optionalValue"
}
"""

struct Response: Decodable {
    let stringField: String
    let intField: Int
    let floatField: Double
    let optionalField: String?
}

let response1 = try! JSONDecoder().decode(Response.self, from: jsonString1.data(using: .utf8)!)
// Response(stringField: "stringValue", intField: 1, floatField: 1.1, optionalField: nil)

let response2 = try! JSONDecoder().decode(Response.self, from: jsonString2.data(using: .utf8)!)
// Response(stringField: "stringValue", intField: 1, floatField: 1.1, optionalField: Optional("optionalValue"))

And one more example, with a nested object:

import Foundation

let jsonString = """
{
    "stringField" : "stringValue",
    "nestedObject" :
        {
            "intValue" : 2
        }
}
"""

struct Response: Decodable {
    
    struct NestedType: Decodable {
        let intValue: Int
    }
    
    let stringField: String
    let nestedObject: NestedType
    
}

let response = try! JSONDecoder().decode(Response.self, from: jsonString.data(using: .utf8)!)
// Response(stringField: "stringValue", nestedObject: __lldb_expr_10.Response.NestedType(intValue: 2))

An example with a date:

import Foundation

let jsonString = """
{
    "date" : "2021-02-04 09:38:33.000"
}
"""

struct Response: Decodable {
    let date: Date
}

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let response = try! decoder.decode(Response.self, from: jsonString.data(using: .utf8)!)
// Response(date: 2021-02-04 09:38:33 +0000)

Find out more here.

UPDATE

After discussion it became clear that the problem is that the object to parse is a dictionary containing objects that cannot be represented in JSON natively (e.g. Data/NSData). In this case it's possible to convert them into something representable (e.g. for dates it can be a formatted String). Here's an example involving NSDate and JSONSerialization:

import Foundation

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

let jsonDict1 = ["stringField" : "stringValue"]
let jsonData1 = try JSONSerialization.data(withJSONObject: jsonDict1)

let date = NSDate()
let jsonDict2: [String : Any] = ["stringField" : "stringValue",
                                 "optionalDate" : dateFormatter.string(from: date as Date)]
let jsonData2 = try JSONSerialization.data(withJSONObject: jsonDict2)

struct Response: Decodable {
  let stringField: String
  let optionalDate: Date?
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let response1 = try! decoder.decode(Response.self, from: jsonData1)
// Response(stringField: "stringValue", optionalDate: nil)

let response2 = try! decoder.decode(Response.self, from: jsonData2)
// Response(stringField: "stringValue", optionalDate: Optional(2022-02-21 13:48:48 +0000))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1