Home > Software design >  How to recursively compare two Codable objects and get differences in Swift?
How to recursively compare two Codable objects and get differences in Swift?

Time:07-20

I have two instances of Lecture which are oldLecture and newLecture. A simplified declaration of Lecture is as follows (the full version has many more fields):

struct Lecture: Codable {
    let title: String
    let class_times: [ClassTime]
    let color: [String: String]
}

struct ClassTime: Codable {
    let start: Double
    let len: Double
}

Now I need to recursively compare oldLecture and newLecture, and create a new dictionary-like object that contains only the properties that are modified in newLecture. The resulting object will be sent to the server as PUT request.

For example,

var oldLecture = Lecture(title: "lecture1", 
                         class_times: [ClassTime(start: 1, len: 2)], 
                         color: ["fg": "#fff", "bg": "#000"])
var newLecture = Lecture(title: "lecture1", 
                         class_times: [ClassTime(start: 2, len: 3)], 
                         color: ["fg": "#fff", "bg": "#000"])

// the result object should only contain `class_times` key with the value of `[ClassTime(start: 2, len: 3)]`

What I have tried so far

guard let oldDict = oldLecture.asDictionary() else { return nil }
guard let newDict = newLecture.asDictionary() else { return nil }
var dict: [String: Any] = [:]
for (key, newVal) in newDict {
    if let oldVal = oldDict[key], oldVal != newVal {
        dict[key] = newVal
    }
}

The approach above won't work because it doesn't compare oldVal and newVal recursively.

Is there a cleaner way to achieve this, without using third-party libraries such as SwiftyJSON?

CodePudding user response:

to get the all the differences between the newLecture and oldLecture (or vice versa), and send that to your server, try this approach, using Equatable for ClassTime, that will compare the content of ClassTime objects. The results of getDiff(...) gives you a Lecture, with the title differences in the title field, and similarly for color and class_times fields.

 let result = getDiff(old: oldLecture, new: newLecture)
 print("\n---> result: \(result)")
 do {
     let data = try JSONEncoder().encode(result)
     print("---> data: \(data)")
     // --> send data to your server
     if let str = String(data: data, encoding: .utf8) {
         print("---> this is the json object you are sending: \(str)")
     }
 } catch {
     print("---> error: \(error)")
 }
 
 where:
 
 func getDiff(old: Lecture, new: Lecture) -> Lecture {
     let title = old.title == new.title ? "" : new.title
     let color: [String: String] = new.color.filter{ dic in
         !old.color.contains(where: { $0.key == dic.key && $0.value == dic.value })
     }
     let cls = new.class_times.filter{ !old.class_times.contains($0)}
     return Lecture(title: title, class_times: cls, color: color)
 }
 

with:

 struct ClassTime: Codable, Equatable {  // <-- here
     let start: Double
     let len: Double
 }

CodePudding user response:

I think, you want to look into Sets and Set Arithmetics.
This code is probably not fully what you want, but it should point you in the right direction.

By creating a symetric different set of the times of both lectures we get a list of times that are in either lecture, but not both.

struct Lecture: Codable, Equatable {
    let title: String
    let times: [LectureTime]
    let color: [String: String]
}

struct LectureTime: Codable, Hashable {
    let start: Double
    let len: Double
}

var oldLecture = Lecture(title: "lecture1",
                         times: [.init(start: 1, len: 2), .init(start: 5, len: 2)],
                         color: ["fg": "#fff", "bg": "#000"])
var newLecture = Lecture(title: "lecture1",
                         times: [.init(start: 2, len: 3), .init(start: 5, len: 2)],
                         color: ["fg": "#fff", "bg": "#000"])


if oldLecture != newLecture {
    let oldTimes = Set(oldLecture.times)
    let newTimes = Set(newLecture.times)
    let x = oldTimes.symmetricDifference(newTimes)
    print(String(describing:x))
}

prints

[LectureTime(start: 1.0, len: 2.0), LectureTime(start: 2.0, len: 3.0)]

Probably you want to extend your time model to be identifiable— than you could identify, what time has been changed.

  • Related