0

I know JSON keys don't have any order and can be generated randomly, but there isn't anything preventing me from writing this function and from my tests this works on my every use case I tested.

func ==<T: Encodable> (lhs: T, rhs: T) -> Bool {
   let encoder = JSONEncoder()
   do {
       let leftEncoded = try encoder.encode(lhs)
       let rightEncoded = try encoder.encode(rhs)
       return leftEncoded == rightEncoded
   } catch {
       return false
   }
}

The problem I want to solve is writing a function for a type which has an array of one protocol with like 20 different implementation, which I have to implement the == function instead of swift auto synthesise. And I know I can switch to JSONSerialization.writeJSONObject with option .sortedKeys to persist the keys order.

What is the downside of this implementation instead of any other method of writing == function?

Ali Riahipour
  • 524
  • 6
  • 20
  • 1
    Encdoing the objects to JSON and then comparing the two encoded `Data` objects seems like an unnecessary overhead when you could simply make `T` conform to `Equatable`, especially since Swift 4.1 can synthesise `Equatable` conformance automatically for most types. – Dávid Pásztor Oct 29 '18 at 15:59
  • `return try JSONEncoder().encode(lhs) == JSONEncoder().encode(rhs)` – Leo Dabus Oct 29 '18 at 16:16
  • @LeoDabus That would make this function throwing. – Rob Napier Oct 29 '18 at 16:19
  • @RobNapier I didn't say to remove the `do catch`. `do { return try JSONEncoder().encode(lhs) == JSONEncoder().encode(rhs) } catch { print(error) return false }` – Leo Dabus Oct 29 '18 at 16:19

2 Answers2

2

The answer seems to be in your question: "JSON keys don't have any order." Also, two things that are logically equal might encode differently (if their equality is dependent only on their id for example). Also, reference types that encode the same values may not be equatable at all (UIView for example). Also, this creates a very broad == overload that makes type-checking more expensive (much as + is extremely expensive).

What you're doing is trying to auto-conform to Equatable when it can be synthesized. Swift could have done this; in fact, for most Encodable cases, Equatable can be synthesized just by adding : Equatable. The Swift team explicitly chose not to auto-conform in those cases and force you to request it when you mean it, because it doesn't always mean "all the properties contain the same bits." When the Swift team explicitly chooses to avoid a feature, it is rarely a good idea to re-add it.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • The problem I want to solve is writing a function for a type which has an array of one protocol with like 20 different implementation, which I have to implement the `==` function instead of swift auto synthesise. And I know I can switch to `JSONSerialization.writeJSONObject` with option `.sortedKeys` to persist the keys order. You mentioned overload for `==` function, is it for compile time or also runtime? – Ali Riahipour Oct 30 '18 at 08:53
  • Added this description to the question – Ali Riahipour Oct 30 '18 at 08:55
  • 1
    It's a compile-time problem. You can achieve the above without taking on unintended side-effects by attaching this to your protocol types rather than to all Encodables. – Rob Napier Oct 30 '18 at 14:09
0
func ==<T: Codable> (lhs: T, rhs: T) -> Bool {
    let encoder = JSONEncoder()

    guard let lhsData = try? encoder.encode(lhs),
          let rhsData = try? encoder.encode(lhs),
          let left = try? JSONSerialization.jsonObject(with: lhsData) as? [String : Any],
          let right = try? JSONSerialization.jsonObject(with: rhsData) as? [String : Any] else { return false }

    let leftDictionary = left as NSDictionary
    let rightDictionary = right

    return leftDictionary.isEqual(to: rightDictionary)
}
user1447414
  • 1,306
  • 2
  • 12
  • 25
  • I think that the second assignment inside the `guard` is wrong, instead of `lhs` in `let rhsData = try? encoder.encode(lhs)` it should be `rhs`. – ideastouch Jun 17 '21 at 22:21