60 votes

Custom Swift Encoder / Decoder for Format de ressource de chaîne

Je me suis amusé avec Codable et j'ai lu et écrit du JSON depuis et vers un fichier. Maintenant, j'aimerais écrire un fichier personnalisé Coder qui peut lire et écrire des fichiers iOS .strings . Est-ce que quelqu'un peut m'aider avec ça? J'ai trouvé les protocoles Encoder et Decoder , mais je n'ai aucune idée de ce que je devrais mettre en œuvre ici:

 class StringsEncoder {}

extension StringsEncoder: Encoder {
    var codingPath: [CodingKey?] {
        return []
    }

    var userInfo: [CodingUserInfoKey : Any] {
        return [:]
    }

    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {

    }

    func unkeyedContainer() -> UnkeyedEncodingContainer {

    }

    func singleValueContainer() -> SingleValueEncodingContainer {

    }
}

extension StringsEncoder: Decoder {
    func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {

    }

    func unkeyedContainer() throws -> UnkeyedDecodingContainer {

    }

    func singleValueContainer() throws -> SingleValueDecodingContainer {

    }
}
 

59voto

Paulo Mattos Points 10791

Un peu en retard pour la fête ici mais je pense qu'il pourrait être utile, intéressant pour d'autres, la question étant de savoir à haute voix comptent. (Je ne vais pas entrer dans la réelle utilité d'un tel code dans la pratique-s'il vous plaît vérifier les commentaires ci-dessus pour cela.)

Malheureusement, étant donné le codage de la pile de la souplesse et de solidité, la mise en œuvre d'un nouveau codage et de décodage de la solution, une alternative à la représentation externe, est loin d'être une tâche facile...

L'encodage

Commençons par la mise en œuvre de l' encodage de la partie pour le fichier de chaînes de la représentation externe. (Les types nécessaires seront introduites dans une approche de haut en bas.)

Comme la norme JSONEncoder classe nous avons besoin d'introduire une classe pour exposer/lecteur de notre nouveau codage de l'API. Disons qu' StringsEncoder:

/// An object that encodes instances of a data type 
/// as strings following the simple strings file format.
public class StringsEncoder {

    /// Returns a strings file-encoded representation of the specified value. 
    public func encode<T: Encodable>(_ value: T) throws -> String {
        let stringsEncoding = StringsEncoding()
        try value.encode(to: stringsEncoding)
        return dotStringsFormat(from: stringsEncoding.data.strings)
    }

    private func dotStringsFormat(from strings: [String: String]) -> String {
        var dotStrings = strings.map { "\"\($0)\" = \"\($1)\";" }
        dotStrings.sort()
        dotStrings.insert("/* Generated by StringsEncoder */", at: 0)
        return dotStrings.joined(separator: "\n")
    }
}

Ensuite, nous avons besoin de fournir un type (par exemple, un struct) conforme à la base Encoder protocole:

fileprivate struct StringsEncoding: Encoder {

    /// Stores the actual strings file data during encoding.
    fileprivate final class Data {
        private(set) var strings: [String: String] = [:]

        func encode(key codingKey: [CodingKey], value: String) {
            let key = codingKey.map { $0.stringValue }.joined(separator: ".")
            strings[key] = value
        }
    }

    fileprivate var data: Data

    init(to encodedData: Data = Data()) {
        self.data = encodedData
    }

    var codingPath: [CodingKey] = []

    let userInfo: [CodingUserInfoKey : Any] = [:]

    func container<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
        var container = StringsKeyedEncoding<Key>(to: data)
        container.codingPath = codingPath
        return KeyedEncodingContainer(container)
    }

    func unkeyedContainer() -> UnkeyedEncodingContainer {
        var container = StringsUnkeyedEncoding(to: data)
        container.codingPath = codingPath
        return container
   }

    func singleValueContainer() -> SingleValueEncodingContainer {
        var container = StringsSingleValueEncoding(to: data)
        container.codingPath = codingPath
        return container
    }
}

Enfin, nous devons traiter tous les 3 codage des conteneurs de types:

  • KeyedEncodingContainer
  • UnkeyedEncodingContainer
  • SingleValueEncodingContainer
fileprivate struct StringsKeyedEncoding<Key: CodingKey>: KeyedEncodingContainerProtocol {

    private let data: StringsEncoding.Data

    init(to data: StringsEncoding.Data) {
        self.data = data
    }

    var codingPath: [CodingKey] = []

    mutating func encodeNil(forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: "nil")
    }

    mutating func encode(_ value: Bool, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: String, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value)
    }

    mutating func encode(_ value: Double, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: Float, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: Int, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: Int8, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: Int16, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: Int32, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: Int64, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: UInt, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: UInt8, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: UInt16, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: UInt32, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode(_ value: UInt64, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }

    mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath.append(key)
        try value.encode(to: stringsEncoding)
    }

    mutating func nestedContainer<NestedKey: CodingKey>(
        keyedBy keyType: NestedKey.Type,
        forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
        var container = StringsKeyedEncoding<NestedKey>(to: data)
        container.codingPath = codingPath + [key]
        return KeyedEncodingContainer(container)
    }

    mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
        var container = StringsUnkeyedEncoding(to: data)
        container.codingPath = codingPath + [key]
        return container
    }

    mutating func superEncoder() -> Encoder {
        let superKey = Key(stringValue: "super")!
        return superEncoder(forKey: superKey)
    }

    mutating func superEncoder(forKey key: Key) -> Encoder {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath = codingPath + [key]
        return stringsEncoding
    }
}
fileprivate struct StringsUnkeyedEncoding: UnkeyedEncodingContainer {

    private let data: StringsEncoding.Data

    init(to data: StringsEncoding.Data) {
        self.data = data
    }

    var codingPath: [CodingKey] = []

    private(set) var count: Int = 0

    private mutating func nextIndexedKey() -> CodingKey {
        let nextCodingKey = IndexedCodingKey(intValue: count)!
        count += 1
        return nextCodingKey
    }

    private struct IndexedCodingKey: CodingKey {
        let intValue: Int?
        let stringValue: String

        init?(intValue: Int) {
            self.intValue = intValue
            self.stringValue = intValue.description
        }

        init?(stringValue: String) {
            return nil
        }
    }

    mutating func encodeNil() throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: "nil")
    }

    mutating func encode(_ value: Bool) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: String) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value)
    }

    mutating func encode(_ value: Double) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: Float) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: Int) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: Int8) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: Int16) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: Int32) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: Int64) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: UInt) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: UInt8) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: UInt16) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: UInt32) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode(_ value: UInt64) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }

    mutating func encode<T: Encodable>(_ value: T) throws {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath = codingPath + [nextIndexedKey()]
        try value.encode(to: stringsEncoding)
    }

    mutating func nestedContainer<NestedKey: CodingKey>(
        keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
        var container = StringsKeyedEncoding<NestedKey>(to: data)
        container.codingPath = codingPath + [nextIndexedKey()]
        return KeyedEncodingContainer(container)
    }

    mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
        var container = StringsUnkeyedEncoding(to: data)
        container.codingPath = codingPath + [nextIndexedKey()]
        return container
    }

    mutating func superEncoder() -> Encoder {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath.append(nextIndexedKey())
        return stringsEncoding
    }
}
fileprivate struct StringsSingleValueEncoding: SingleValueEncodingContainer {

    private let data: StringsEncoding.Data

    init(to data: StringsEncoding.Data) {
        self.data = data
    }

    var codingPath: [CodingKey] = []

    mutating func encodeNil() throws {
        data.encode(key: codingPath, value: "nil")
    }

    mutating func encode(_ value: Bool) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: String) throws {
        data.encode(key: codingPath, value: value)
    }

    mutating func encode(_ value: Double) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: Float) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: Int) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: Int8) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: Int16) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: Int32) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: Int64) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: UInt) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: UInt8) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: UInt16) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: UInt32) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode(_ value: UInt64) throws {
        data.encode(key: codingPath, value: value.description)
    }

    mutating func encode<T: Encodable>(_ value: T) throws {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath = codingPath
        try value.encode(to: stringsEncoding)
    }
}

Évidemment, j'ai fait quelques décisions de conception sur la façon de coder les types imbriqués à l'aide de la (très!) simple fichier de chaînes de format. J'espère que mon code est assez clair qu'il devrait être facile de modifier l'encodage de détails si vous le souhaitez.

Tests

Un test simple pour un trivial Codable type:

struct Product: Codable {
    var name: String
    var price: Float
    var info: String
}

let iPhone = Product(name: "iPhone X", price: 1_000, info: "Our best iPhone yet!")

let stringsEncoder = StringsEncoder()
do {
    let stringsFile = try stringsEncoder.encode(iPhone)
    print(stringsFile)
} catch {
    print("Encoding failed: \(error)")
}

Sortie:

/* Generated by StringsEncoder */
"info" = "Our best iPhone yet!";
"name" = "iPhone X";
"price" = "1000.0";

Un test plus complexes avec imbriqués les structures et les tableaux:

struct Product: Codable {
    var name: String
    var price: Float
    var info: String
}

struct Address: Codable {
    var street: String
    var city: String
    var state: String
}

struct Store: Codable {
    var name: String
    var address: Address // nested struct
    var products: [Product] // array
}

let iPhone = Product(name: "iPhone X", price: 1_000, info: "Our best iPhone yet!")
let macBook = Product(name: "Mac Book Pro", price: 2_000, info: "Early 2019")
let watch = Product(name: "Apple Watch", price: 500, info: "Series 4")

let appleStore = Store(
    name: "Apple Store",
    address: Address(street: "300 Post Street", city: "San Francisco", state: "CA"),
    products: [iPhone, macBook, watch]
)

let stringsEncoder = StringsEncoder()
do {
    let stringsFile = try stringsEncoder.encode(appleStore)
    print(stringsFile)
} catch {
    print("Encoding failed: \(error)")
}

Sortie:

/* Generated by StringsEncoder */
"address.city" = "San Francisco";
"address.state" = "CA";
"address.street" = "300 Post Street";
"name" = "Apple Store";
"products.0.info" = "Our best iPhone yet!";
"products.0.name" = "iPhone X";
"products.0.price" = "1000.0";
"products.1.info" = "Early 2019";
"products.1.name" = "Mac Book Pro";
"products.1.price" = "2000.0";
"products.2.info" = "Series 4";
"products.2.name" = "Apple Watch";
"products.2.price" = "500.0";

Décodage

Compte tenu de la façon dont grand cette réponse est déjà le cas, je vais laisser le décodage de la partie (c'est à dire, de la création de l' StringsDecoder classe, conformément à l' Decoder protocole, etc) comme un exercice pour le lecteur... s'il vous plaît laissez-moi savoir si vous les gars ont besoin d'aide avec ça ;)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X