mike4aday / SwiftlySalesforce

The Swift-est way to build native mobile apps that connect to Salesforce.
MIT License
136 stars 43 forks source link

Error in fields array: Protocol 'Encodable' as a type cannot conform to the protocol itself #133

Closed perbrondum closed 3 years ago

perbrondum commented 3 years ago

Used to use [String : Encodable] as type for fields array. In 9.0 Error: Protocol 'Encodable' as a type cannot conform to the protocol itself

let  fields : [String : Encodable] = [keys.Subject.rawValue: self.subjectInp.text! ,...]
DB.salesforce.insert(type: objects.Event.rawValue, fields: eventNewFields)
perbrondum commented 3 years ago

So this is more of a swift issue and not related to swiftlysalesforce 9.

Thanks. Per

On Aug 4, 2021, at 6:34 PM, Michael Epstein @.***> wrote:

 Hi @perbrondum see: https://stackoverflow.com/questions/62252547/value-of-protocol-type-encodable-cannot-conform-to-encodable-only-struct-en

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

mike4aday commented 3 years ago

@perbrondum yes, correct. Try a concrete type that conforms to Encodable instead, e.g. let fields: [String: String] = ...

perbrondum commented 3 years ago

The whole point about using Encodable, I thought, was to use Encodable (Any) as field values can be a mix of strings/int/Double/Dates.

Most values are Strings, but Account.AnnualRevenue is an Int, Opportunity.Amount is a Double etc.

Per B Jakobsen CEO Tap2Sales.com @.***

On Aug 4, 2021 at 9:54:49 PM, Michael Epstein @.***> wrote:

@perbrondum https://github.com/perbrondum yes, correct. Try a concrete type that conforms to Encodable instead, e.g. let fields: [String: String] = ...

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mike4aday/SwiftlySalesforce/issues/133#issuecomment-892931370, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALCYWDGQJR5QWPXWXH4LQKTT3GLITANCNFSM5BLG2T4A .

mike4aday commented 3 years ago

@perbrondum - you could use a type-erased wrapper, e.g. AnyEncodable:

let app = try ConnectedApp()
let fields: [String : AnyEncodable] = [
  "Name" : "Acme Corp., Inc.",
  "Revenue__c" : 123456.35,
  "Employee_Count__c" : 23,
  "US_Based__c" : true]
let _ = app.insert(type: "Account", fields: fields)

But I believe it's simpler to just apply a map function to each field value and convert it to a String before passing to insert.

perbrondum commented 3 years ago

I tried installing AnyCodable, but that just seems to create more issues.

Are you saying we can pass all values as strings? Consider this:

let eventFields : [String : Encodable] = [keys.Subject.rawValue: self. NewSubject.text! ,"whatId": self.eventWhatId!, "whoId": self.eventWhoId! , "StartDateTime": startDateTimeStr!, "EndDateTime": endDateTimeStr!, "DurationInMinutes": minutesInt!, "IsAllDayEvent": allDay!]

Salesforce expects DurationInMinutes to be an Int, and AllDayEvent to be a Bool. How would mapping to Strings before the insert solve this?

I only have a dozen or so update/insert statements that include mixed type fields, so probably not a big deal, but I would need to find a workaround.

Per B Jakobsen CEO Tap2Sales.com @.***

On Aug 5, 2021 at 3:19:10 AM, Michael Epstein @.***> wrote:

@perbrondum https://github.com/perbrondum - you could use a type-erased wrapper, e.g. AnyEncodable https://github.com/Flight-School/AnyCodable:

let app = try ConnectedApp()let fields: [String : AnyEncodable] = [ "Name" : "Acme Corp., Inc.", "Revenuec" : 123456.35, "Employee_Count__c" : 23, "US_Basedc" : true]let _ = app.insert(type: "Account", fields: fields)

But I believe it's simpler to just apply a map function to each field value and convert it to a String before passing to insert.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mike4aday/SwiftlySalesforce/issues/133#issuecomment-893088961, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALCYWDFDYF4MQPOOFS6UVJ3T3HRI5ANCNFSM5BLG2T4A .

perbrondum commented 3 years ago

I had no idea we could pass all fields as Strings. Was that always the case? Makes life so much easier. Thanks

private func createRecord() throws {

*let* app = *try* ConnectedApp()

*let* oneHour: TimeInterval = 24 * 3600

*let* dateFormatter = DateFormatter()

dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"

*let* startDateTimeStr    = dateFormatter.string(from: Date())

*let* endDateTimeStr      = dateFormatter.string(from: Date().

addingTimeInterval(oneHour))

*let* fields : [String: String] = [

"Subject" : "New Event",

    "StartDateTime" : startDateTimeStr,

    "EndDateTime" : endDateTimeStr,

    "\(NameSpacePrefix)__sentimentValue__c": "3.24",  // double

   // "DurationInMinutes": "86400",   // Int

    "IsAllDayEvent": "true"   // Bool

]

*let* pub: AnyPublisher<String, Error> = app.insert(type: "Event",

fields: fields)

pub.sink { completion *in*

    *switch* completion {

    *case* *let* .failure(error):

        print("Failed to execute insert. Error: \(error)")

    *case* .finished:

        print("Success insert")

        *break*

    }

} receiveValue: { (Id) *in*

   print("Created new Event with id: \(Id)")

}.store(in: &subscriptions)

}

Per B Jakobsen CEO Tap2Sales.com @.***

On Aug 5, 2021 at 12:01:48 PM, Per Jakobsen @.***> wrote:

I tried installing AnyCodable, but that just seems to create more issues.

Are you saying we can pass all values as strings? Consider this:

let eventFields : [String : Encodable] = [keys.Subject.rawValue: self. NewSubject.text! ,"whatId": self.eventWhatId!, "whoId": self. eventWhoId! , "StartDateTime": startDateTimeStr!, "EndDateTime": endDateTimeStr!, "DurationInMinutes": minutesInt!, "IsAllDayEvent": allDay !]

Salesforce expects DurationInMinutes to be an Int, and AllDayEvent to be a Bool. How would mapping to Strings before the insert solve this?

I only have a dozen or so update/insert statements that include mixed type fields, so probably not a big deal, but I would need to find a workaround.

Per B Jakobsen CEO Tap2Sales.com @.***

On Aug 5, 2021 at 3:19:10 AM, Michael Epstein @.***> wrote:

@perbrondum https://github.com/perbrondum - you could use a type-erased wrapper, e.g. AnyEncodable https://github.com/Flight-School/AnyCodable:

let app = try ConnectedApp()let fields: [String : AnyEncodable] = [ "Name" : "Acme Corp., Inc.", "Revenuec" : 123456.35, "Employee_Count__c" : 23, "US_Basedc" : true]let _ = app.insert(type: "Account", fields: fields)

But I believe it's simpler to just apply a map function to each field value and convert it to a String before passing to insert.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mike4aday/SwiftlySalesforce/issues/133#issuecomment-893088961, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALCYWDFDYF4MQPOOFS6UVJ3T3HRI5ANCNFSM5BLG2T4A .

mike4aday commented 3 years ago

@perbrondum as long as Salesforce can deserialize the JSON values from string into the expected type it should work.

FYI you could use Swiftly Salesforce's date formatter and a value map on the fields dictionary:

let fields: [String : String] = [
   "Subject__c" : "My Subject",
   "Revenue__c" : 123456.35,
   "My_Date__c" : Date().addingTimeInterval(-100000),
   "Employee_Count__c" : 23,
   "US_Based__c" : true
]
.mapValues { value in
  switch value {
    case let date as Date:
      return DateFormatter.salesforce.string(from: date)
    default:
      return String(describing: value)
  }
}

let app = try ConnectedApp()
cancellable = app
.insert(type: "My_Object__c", fields: fields)
.sink(receiveCompletion: { completion in
  switch completion {
    case .failure(let error):
      print(error)
    case .finished:
      print("Success")
    }
  }, receiveValue: { 
    print("NEW RECORD ID: \($0)")
  }
)