Are Sets encodable in Swift4 ?

I cannot find a clear answer to this in doc.


It seems it was missing in early Swift4,

SwiftSR-4769 : h ttps://bugs.swift.org/browse/SR-4769?jql=ORDER%20BY%20assignee%20ASC


but should have been resolved as per :

h ttps://github.com/apple/swift/pull/9231


Is this already implemented or to come in a future version of Swift ?

Answered by OOPer in 283571022

This seems to be a clear answer:

Set

(See the bottom of the page, titled Conforms To.)

`Set` conforms to both Encodable and Decodable.


Or you can easily check it in the Playground:

let set: Set<Int> = [1,2,3]
let encoder = JSONEncoder()
let data = try! encoder.encode(set)
print(String(data: data, encoding: .utf8)) //->Optional("[2,3,1]")
let decoder = JSONDecoder()
let decodedSet = try! decoder.decode(Set<Int>.self, from: data)
print(decodedSet) //->[2, 3, 1]

Tested with Xcode 9.2 (9C40b).

Accepted Answer

This seems to be a clear answer:

Set

(See the bottom of the page, titled Conforms To.)

`Set` conforms to both Encodable and Decodable.


Or you can easily check it in the Playground:

let set: Set<Int> = [1,2,3]
let encoder = JSONEncoder()
let data = try! encoder.encode(set)
print(String(data: data, encoding: .utf8)) //->Optional("[2,3,1]")
let decoder = JSONDecoder()
let decodedSet = try! decoder.decode(Set<Int>.self, from: data)
print(decodedSet) //->[2, 3, 1]

Tested with Xcode 9.2 (9C40b).

Thanks, clear.


But it seems that an Array<Array<T>>

where T is encodable cannot be declared as Codable. Or at least, it requires initializers for coding(from) and to.


In fact I get an error

Fatal error: Array<Cell> does not conform to Encodable because Cell does not conform to Encodable.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.72/src/swift/stdlib/public/core/Codable.swift, line 3962


where Cell is defined as :

typealias MySet = Set<Int>
struct Cell: Codable {
    var classesEntieres : MySet = []
    var classesDemiA : MySet = []
    var classesDemiB : MySet = []
    var m : Int = -1  
    var s : Int = -1
    var a : Int = -1
}


Fail occurs here (names have been edited):

typealias DoubleArray = Array<Array<Cell>>
struct AStruct: Codable {
    var tt : Array<Array<Cell>> = []

    enum CodingKeys: String, CodingKey {
        case tt = "theKey"
    }

    init(withT : DoubleArray) {
        self.tt = withT
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.tt = try values.decode(Array<Array<Cell>>.self, forKey: .tt)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.tt, forKey: .tt)     // FAILURE HERE
    }
}

The encode is called from try archiver.encodeEncodable:

    let valueToSave = DoubleArray()
    let data = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWith: data)
    do {
        try archiver.encodeEncodable(valueToSave, forKey: "theKey")  // I use the same string, but is it the same key ?
    }
    catch {
        Swift.print("Unable to encodeEncodable", error)
    }


I see no other reason for Cell does not conform to Encodable but an issue with Set ? Or am I totally misusing Codable in link with archiver ?


Note: I saw in Swift.org h ttps://github.com/apple/swift/blob/master/stdlib/public/core/Codable.swift

an extension for sets, but cannot use it yet.


extension Set : Encodable where Element : Encodable {
    @_inlineable // FIXME(sil-serialize-all)
    public func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        for element in self {
            try container.encode(element)
        }
    }
}


extension Set : Decodable where Element : Decodable {
    @_inlineable // FIXME(sil-serialize-all)
    public init(from decoder: Decoder) throws {
        self.init()

        var container = try decoder.unkeyedContainer()
        while !container.isAtEnd {
            let element = try container.decode(Element.self)
            self.insert(element)
        }
    }
}



I am trying to upgarde to 9.2 (not beta) to see if there is a difference)

EDITED : tested with XCode 9.2 (9C40b), no difference. So problem is in my code.

Or am I totally misusing Codable in link with archiver ?

Maybe, but not clear enough.


In your code, `AStruct` itself is `Codable` and you have no need to use `NSKeyedArchiver`:

let cell1 = Cell()
let tt = [[cell1]]
let aStruct = AStruct(withT: tt)
do {
    let encoder = JSONEncoder()
    let encodedData = try encoder.encode(aStruct)
    print(String(data: encodedData, encoding: .utf8) ?? "nil") //->{"theKey":[[{"m":-1,"classesDemiB":[],"s":-1,"classesEntieres":[],"a":-1,"classesDemiA":[]}]]}
    let decoder = JSONDecoder()
    let decodedStruct = try decoder.decode(AStruct.self, from: encodedData)
    print(decodedStruct)  //->AStruct(tt: [[__lldb_expr_1.Cell(classesEntieres: Set([]), classesDemiA: Set([]), classesDemiB: Set([]), m: -1, s: -1, a: -1)]])
} catch {
    print(error)
}


If you do want to use `NSKeyedArchiver` with `Codable`, you can write something like this:

class AClass: NSObject, NSCoding {
    var tt : Array<Array<Cell>> = []

    init(withT : DoubleArray) {
        self.tt = withT
    }
    func encode(with aCoder: NSCoder) {
        guard let keyedArchiver = aCoder as? NSKeyedArchiver else {
            fatalError("Needs NSKeyedArchiver")
        }
        do {
            try keyedArchiver.encodeEncodable(tt, forKey: "theKey")
        } catch {
            print("Unable to encodeEncodable", error)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        guard let keyedUnarchiver = aDecoder as? NSKeyedUnarchiver else {
            fatalError("Needs NSKeyedUnarchiver")
        }
        if let theKey = keyedUnarchiver.decodeDecodable(Array<Array<Cell>>.self, forKey: "theKey") {
            tt = theKey
        } else {
            print("Unable to decodeDecodable")
        }
    }
}
let anObject = AClass(withT: tt)
let archivedData = NSKeyedArchiver.archivedData(withRootObject: anObject)
print(String(data: archivedData, encoding: .utf8) ?? "nil") //->nil
let unarchivedObject = NSKeyedUnarchiver.unarchiveObject(with: archivedData)
print(unarchivedObject) //->Optional(<__lldb_expr_1.AClass: 0x7f88269cd200>)


But I do not understand what you are trying with your fragment of code.

What I try is writing the struct to a file ;


Previously, I used a class instead of struct, so it worked. Now, I try to replace class by a struct, hence the problems.


In fact I have several struct to save, that's why I used NSArchiver.


But that was an early design decision, maybe not a good one ?

>> In your code, `AStruct` itself is `Codable` and you have no need to use `NSKeyedArchiver`:


That's not really a valid conclusion (in general) because the semantics of NSKeyedArchiver are different from the semantics of JSONEncoder. NSKeyedArchiver ensures each object (instance) is encoded exactly once in the archive, no matter how many references there are in the object graph presented for archiving. JSONEncoder encodes only values, so multiple references to a single object on encoding will become multiple objects on decoding, which is not the correct behavior for an archive.


TBH, I can no longer understand what question this thread is asking, after the initial question was answered. Claude, if there's something still in question, perhaps a new thread would be appropriate? Also, we need to be clear about which version of Xcode this is (you seemed to say you weren't using 9.2 yet), and which version of the Swift compiler (your error messages seem to indicate a version with debug symbols, such as a nightly build or something).

Are Sets encodable in Swift4 ?
 
 
Q