Swift 4 добавил новый Codable
протокол. Когда я JSONDecoder
его использую, кажется, что все необязательные свойства моего Codable
класса должны иметь ключи в JSON, иначе возникает ошибка.
Делать каждое свойство моего класса необязательным кажется ненужной проблемой, поскольку я действительно хочу использовать значение в json или значение по умолчанию. (Я не хочу, чтобы свойство было равно нулю.)
Есть ли способ сделать это?
class MyCodable: Codable {
var name: String = "Default Appleseed"
}
func load(input: String) {
do {
if let data = input.data(using: .utf8) {
let result = try JSONDecoder().decode(MyCodable.self, from: data)
print("name: \(result.name)")
}
} catch {
print("error: \(error)")
// `Error message: "Key not found when expecting non-optional type
// String for coding key \"name\""`
}
}
let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional
Ответы:
Подход, который я предпочитаю, заключается в использовании так называемых DTO - объекта передачи данных. Это структура, соответствующая Codable и представляющая желаемый объект.
Затем вы просто инициализируете объект, который хотите использовать в приложении, с этим DTO.
Этот подход также хорош, поскольку вы можете переименовывать и изменять конечный объект по своему усмотрению. Он ясен и требует меньше кода, чем ручное декодирование. Более того, с помощью этого подхода вы можете отделить сетевой уровень от другого приложения.
источник
Вы можете реализовать
init(from decoder: Decoder)
метод в своем типе вместо использования реализации по умолчанию:Вы также можете сделать
name
постоянное свойство (если хотите):или
Повторите свой комментарий: С настраиваемым расширением
вы можете реализовать метод инициализации как
но это не намного короче, чем
источник
CodingKeys
перечисление (так что можете удалить настраиваемое определение) :)ObjectMapper
справляется с этим очень хорошо.Decodable
и также предоставляете свою собственную реализациюinit(from:)
? В этом случае компилятор предполагает, что вы хотите самостоятельно обрабатывать декодирование, и поэтому не синтезируетCodingKeys
для вас перечисление. Как вы говорите, согласование сCodable
вместо этого работает, потому что теперь компилятор синтезируетencode(to:)
за вас, а также синтезируетCodingKeys
. Если вы также предоставите свою собственную реализациюencode(to:)
,CodingKeys
больше не будет синтезироваться.Одним из решений было бы использование вычисляемого свойства, которое по умолчанию имеет желаемое значение, если ключ JSON не найден. Это добавляет некоторую дополнительную многословность, поскольку вам нужно будет объявить другое свойство, и потребуется добавить
CodingKeys
перечисление (если его еще нет). Преимущество состоит в том, что вам не нужно писать собственный код декодирования / кодирования.Например:
источник
Вы можете реализовать.
источник
Если вы не хотите реализовывать свои методы кодирования и декодирования, есть несколько грязное решение для значений по умолчанию.
Вы можете объявить свое новое поле как неявно развернутое необязательным и проверить, не является ли оно нулевым после декодирования, и установить значение по умолчанию.
Я тестировал это только с помощью PropertyListEncoder, но я думаю, что JSONDecoder работает так же.
источник
Я столкнулся с этим вопросом в поисках того же самого. Ответы, которые я нашел, были не очень удовлетворительными, хотя я боялся, что решения здесь будут единственным вариантом.
В моем случае для создания собственного декодера потребуется тонна шаблонов, которые было бы трудно поддерживать, поэтому я продолжал искать другие ответы.
Я наткнулся на эту статью, в которой показан интересный способ решить эту проблему в простых случаях с помощью
@propertyWrapper
. Самым важным для меня было то, что он был многоразовым и требовал минимального рефакторинга существующего кода.В статье предполагается случай, когда вы хотите, чтобы отсутствующее логическое свойство по умолчанию было равно false без сбоев, но также показаны другие различные варианты. Вы можете прочитать его более подробно, но я покажу, что я сделал для своего варианта использования.
В моем случае у меня было сообщение,
array
которое я хотел инициализировать как пустой, если ключ отсутствовал.Итак, я объявил следующие
@propertyWrapper
и дополнительные расширения:Преимущество этого метода состоит в том, что вы можете легко решить проблему в существующем коде, просто добавив
@propertyWrapper
к свойству. В моем случае:Надеюсь, это поможет кому-то решить ту же проблему.
ОБНОВИТЬ:
После публикации этого ответа, продолжая изучать этот вопрос, я нашел эту другую статью, но, что наиболее важно, соответствующую библиотеку, которая содержит некоторые общие простые в использовании
@propertyWrapper
s для таких случаев:https://github.com/marksands/BetterCodable
источник
Если вы думаете, что написание собственной версии
init(from decoder: Decoder)
является непосильной задачей, я бы посоветовал вам реализовать метод, который будет проверять ввод перед отправкой его в декодер. Таким образом, у вас будет место, где вы можете проверить отсутствие полей и установить свои собственные значения по умолчанию.Например:
А чтобы запустить объект из json, вместо:
Init будет выглядеть так:
В этой конкретной ситуации я предпочитаю иметь дело с необязательными элементами, но если у вас другое мнение, вы можете сделать свой метод customDecode (:) перекидным.
источник