Skip to content
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

Realm Objects with Date variables will never use JSONEncoder.dateEncodingStrategy parameter when not implementing encode(to encoder:) method at encoding time #8425

Closed
rursache opened this issue Nov 28, 2023 · 2 comments · Fixed by #8443

Comments

@rursache
Copy link

rursache commented Nov 28, 2023

How frequently does the bug occur?

Always

Description

I'm trying to encode my Realm Objects so they can be sent to a backend API.

My objects contain multiple Data variables. These should be in a fixed specified format when reaching the API however the resulting Data/JSON String will always encode the dates using timeIntervalSinceReferenceDate instead of the custom JSONEncoder parameters.

However, it works fine the moment I implement encode(to encoder:) method in said object. I would like to not add this amount of boilerplate code to each model as our app have multiple and large objects. This should not be required because Codable objects work as expected, just not with the Object inheritance.

I can reproduce this everytime even on a sample project.

Stacktrace & log output

No crashes or logs showing the issue

Can you reproduce the bug?

Always

Reproduction Steps

Models.swift:

struct SyncResponseModel: Codable {
    let location: SyncLocationDataModel
}

struct SyncLocationDataModel: Codable {
    let workouts: [MyObject]
}

class MyObject: Object, Codable {
    @Persisted(primaryKey: true) var id: UUID
    @Persisted var testDate: Date
    
    enum CodingKeys: String, CodingKey {
        case id, testDate
    }
    
//    the issue is gone if i uncomment this:
//
//    func encode(to encoder: Encoder) throws {
//        var container = encoder.container(keyedBy: CodingKeys.self)
//        try container.encode(id, forKey: .id)
//        try container.encode(testDate, forKey: .testDate)
//    }
}

ViewController.swift:

private var demoId: UUID!

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.writeObject()
}
    
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    self.runTests()
}

private func writeObject() {
    RealmManager.shared.deleteAllObjects() // `RealmManager` is a manager class that communicates with realm
       
    let modelTest = MyObject() // 
    modelTest.testDate = Date()
    RealmManager.shared.addObject(object: modelTest, update: .all)
    
    self.demoId = modelTest.id
}
    
private func runTests() {
    guard let modelTest = RealmManager.shared.getObject(type: MyObject.self, uuid: self.demoId) else {
        fatalError()
    }
    
    let myObjectUnmanaged = MyObject(value: modelTest)
    let location = SyncLocationDataModel(workouts: [myObjectUnmanaged])
    
    do {
        let encoder = JSONEncoder()
        // this is never called:
        encoder.dateEncodingStrategy = .custom { (date, encoder) in
            print("\n dateEncodingStrategy called \n")
            
            let formattedDate = Utils.shared.dateFormatter(format: "yyyy-MM-dd").string(from: date)
            
            var container = encoder.singleValueContainer()
            try container.encode(formattedDate)
        }
        // alternatively, this is never used:
        // encoder.dateEncodingStrategy = .formatted(Utils.shared.dateFormatter(format: "yyyy-MM-dd"))
    
        let jsonString = try String(data: encoder.encode(location), encoding: .utf8)
    
        // the `MyObject.testDate` here is `722892731.604744` instead of the `yyyy-MM-dd` format
        print(jsonString ?? "no json") 
    } catch {
        print(String(describing: error))
    }
}

The resulting jsonString looks like this:

{
  "workouts" : [
    {
      "testDate" : 722892731.604744,
      "id" : "E772E4AE-F02F-438C-82C2-38BA7312315B"
    }
  ]
}

Notice how testDate is a TimeInterval instead of being a 2023-11-28

Version

10.44.0

What Atlas Services are you using?

Local Database only

Are you using encryption?

No

Platform OS and version(s)

iOS 17.1 | Swift 5.9

Build environment

Xcode version: 15.0.1 (15A507)
Dependency manager and version: Swift Package Manager

@rursache rursache added the T-Bug label Nov 28, 2023
@rursache rursache changed the title Realm Objects with Date variables will never use JSONEncoder.dateEncodingStrategy parameter when not implementing encode(to encoder:) method Realm Objects with Date variables will never use JSONEncoder.dateEncodingStrategy parameter when not implementing encode(to encoder:) method at encoding time Nov 28, 2023
@tgoyne
Copy link
Member

tgoyne commented Nov 28, 2023

Looks like we need to do encoder.singleValueContainer().encode(value) rather than value.encode(to: encoder) to support this.

Note that Encodable synthesis doesn't work for managed Realm objects, so depending on what you're doing you may need to manually implement it anyway.

@rursache
Copy link
Author

rursache commented Nov 28, 2023

@tgoyne

Looks like we need to do encoder.singleValueContainer().encode(value) rather than value.encode(to: encoder) to support this.

very interesting. where can I make this change until you guys have time? not really sure where to look

Note that Encodable synthesis doesn't work for managed Realm objects, so depending on what you're doing you may need to manually implement it anyway.

I'm aware, that's why I'm doing let unmanagedObject = MyObject(value: myObject) and it's fine enough for me

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants