Я хотел бы иметь разные варианты копирования экземпляров JSONDecoder из экземпляра по умолчанию. Кажется, что JSONDecoder - это class, но для него нет .copy() API.

extension JSONDecoder {
    static let `default` = JSONDecoder()
}

let decoder2: JSONDecoder = .default
decoder2.keyDecodingStrategy = .convertFromSnakeCase

print(JSONDecoder.default.keyDecodingStrategy) // convertFromSnakeCase

В этом примере к JSONDecoder.default применяются мутации decoder2. Как я могу сделать копию decoder2 перед его изменением?

0
TruMan1 25 Фев 2021 в 17:25

2 ответа

Лучший ответ

Причина отсутствия копии в том, что JSONDecoder не соответствует NSCopying. Если вас беспокоит безопасность потоков и вы можете передать свой объект без ссылки, вам нужна структура-оболочка вместо класса и не раскрывайте декодер, объявляющий его закрытым. Таким образом вы сможете инициализировать настраиваемый объект, но не сможете изменить свойства декодера:

struct JSONDecoderWrapper {
    private let decoder = JSONDecoder()
}

extension JSONDecoderWrapper {
    init(dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .base64,
         dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
         keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
         nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .throw,
         userInfo: [CodingUserInfoKey : Any] = [:]) {
        decoder.dataDecodingStrategy = dataDecodingStrategy
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy
        decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy
        decoder.userInfo = userInfo
    }
}
    

extension JSONDecoderWrapper {
    /// Summary:
    /// Decodes a top-level value of the given type from the given JSON representation.
    func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
        try decoder.decode(type, from: data)
    }
}

extension JSONDecoderWrapper {
    var dataDecodingStrategy: JSONDecoder.DataDecodingStrategy { decoder.dataDecodingStrategy }
    var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy { decoder.dateDecodingStrategy }
    var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy { decoder.keyDecodingStrategy }
    var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy { decoder.nonConformingFloatDecodingStrategy }
    var userInfo: [CodingUserInfoKey : Any] { decoder.userInfo }
}

extension JSONDecoderWrapper {
    func copy(dataDecodingStrategy: JSONDecoder.DataDecodingStrategy? = nil,
              dateDecodingStrategy: JSONDecoder.DateDecodingStrategy? = nil,
              keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy? = nil,
              nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy? = nil,
              userInfo: [CodingUserInfoKey : Any]? = nil) -> Self {
       .init(dataDecodingStrategy: dataDecodingStrategy ?? decoder.dataDecodingStrategy, dateDecodingStrategy: dateDecodingStrategy ?? decoder.dateDecodingStrategy, keyDecodingStrategy: keyDecodingStrategy ?? decoder.keyDecodingStrategy, nonConformingFloatDecodingStrategy:  nonConformingFloatDecodingStrategy ?? decoder.nonConformingFloatDecodingStrategy, userInfo: userInfo ?? decoder.userInfo)
    }
}

Тестирование игровой площадки

extension JSONDecoderWrapper {
    static let `default` = JSONDecoderWrapper()
}

JSONDecoderWrapper.default

// let decoder2 = JSONDecoderWrapper.default
// decoder2.keyDecodingStrategy = .convertFromSnakeCase // error Cannot assign to property: 'keyDecodingStrategy' is a get-only property
// decoder2.decoder.keyDecodingStrategy = .convertFromSnakeCase // error 'decoder' is inaccessible due to 'private' protection level

// you can make a copy changing one or mpore specific properties
let decoder2 = JSONDecoderWrapper.default.copy(keyDecodingStrategy: .convertFromSnakeCase)

print(decoder2.dataDecodingStrategy) // "base64\n"
print(decoder2.dateDecodingStrategy) // deferredToDate\n"
print(decoder2.keyDecodingStrategy) // "convertFromSnakeCase\n"
print(decoder2.nonConformingFloatDecodingStrategy) // "throw\n"
print(decoder2.userInfo) // "[:]\n"
1
Leo Dabus 25 Фев 2021 в 16:38

Вы, конечно, можете написать свой собственный метод копирования, свойств для копирования не так уж и много. Или, если вы хотите использовать тот же шаблон для начала, у вас может быть фабричный метод для создания декодера с конфигурацией по умолчанию.

extension JSONDecoder {
    //copy
    func copy() -> JSONDecoder {
        let decoder = JSONDecoder()
        decoder.dataDecodingStrategy = self.dataDecodingStrategy
        decoder.dateDecodingStrategy = self.dateDecodingStrategy
        //...
        return decoder
    }

    //factory
    static func createDefault() -> JSONDecoder {
        let decoder = JSONDecoder()
        decoder.dataDecodingStrategy = .base64
        decoder.dateDecodingStrategy = .iso8601
        //...

        return decoder
    }
}
2
Joakim Danielson 25 Фев 2021 в 14:58