忽略不支持的解码器
Ignoring not supported Decodables
我一直非常高兴地在我当前的项目中使用 Codable
s - 一切都很好,我开箱即用的大部分东西都是内置的 - 完美!不过,最近我偶然发现了第一个 真正的问题 ,它无法按照我想要的方式自动解决。
问题描述
我有一个来自后端的 JSON,这是一个嵌套的东西。看起来像这样
{
"id": "fef08c8d-0b16-11e8-9e00-069b808d0ecc",
"title": "Challenge_Chapter",
"topics": [
{
"id": "5145ea2c-0b17-11e8-9e00-069b808d0ecc",
"title": "Automation_Topic",
"elements": [
{
"id": "518dfb8c-0b18-11e8-9e00-069b808d0ecc",
"title": "Automated Line examle",
"type": "text_image",
"video": null,
"challenge": null,
"text_image": {
"background_url": "",
"element_render": ""
}
},
{
"id": "002a1776-0b18-11e8-9e00-069b808d0ecc",
"title": "Industry 3.0 vs. 4.0: A vision of the new manufacturing world",
"type": "video",
"video": {
"url": "https://www.youtube.com/watch?v=xxx",
"provider": "youtube"
},
"challenge": null,
"text_image": null
},
{
"id": "272fc2b4-0b18-11e8-9e00-069b808d0ecc",
"title": "Classmarker_element",
"type": "challenge",
"video": null,
"challenge": {
"url": "https://www.classmarker.com/online-test/start/",
"description": null,
"provider": "class_marker"
},
"text_image": null
}
]
}
]
}
Chapter 是根对象,它包含一个 Topics 列表,每个主题包含一个 Elements 列表。非常简单,但我遇到了最低级别 Elements。每个 Element 都有一个来自后端的枚举,如下所示:
[ video, challenge, text_image ]
,但是 iOS 应用程序不支持挑战,所以我在 Swift 中的 ElementType 枚举看起来像:
public enum ElementType: String, Codable {
case textImage = "text_image"
case video = "video"
}
当然,它 throws
,因为发生的第一件事是它尝试解码此枚举的 challenge
值,但它不存在,所以我的整个解码都失败了。
我想要的
我只是希望解码过程 ignore Element
s 无法解码。我不需要任何 Optional
。我只是希望它们不要出现在 Topic 的元素数组中。
我的推理及其缺点
当然,我已经做了几次尝试来解决这个问题。第一个,也是最简单的一个,就是将 ElementType
标记为 Optional
,但稍后使用这种方法时,我将不得不解包所有内容并处理它——这是一项相当乏味的任务。我的第二个想法是在我的枚举中有类似 .unsupported
的情况,但是稍后我想用它来生成单元格,我将不得不 throw
或 return Optional
- 基本上,与以前的想法相同的问题。
我的最后一个想法,但我还没有测试过,是为 decodable 编写自定义 init()
并以某种方式在那里处理它,但我不确定它是 Element
还是 Topic
这应该由谁负责?如果我把它写在Element
,我不能return什么,我将不得不throw
,但如果我把它写在Topic
,我将不得不append
成功解码元素到数组。事情是如果在某个时候我将直接获取 Elements
会发生什么 - 再一次,如果没有 throw
ing 我将无法做到这一点。
TL;DR
我想要 init(from decoder: Decoder) throws
不是 throw
,而是 return Optional
.
我建议为所有三种类型创建一个伞式协议
protocol TypeItem {}
编辑:为了符合只能考虑两种类型的要求,您必须使用 classes 来获取引用语义
然后创建classes TextImage
和Video
和一个Dummy
class采用协议。 Dummy class
的所有实例都将在解码过程后删除。
class TextImage : TypeItem, Decodable {
let backgroundURL : String
let elementRender : String
private enum CodingKeys : String, CodingKey {
case backgroundURL = "background_url"
case elementRender = "element_render"
}
}
class Video : TypeItem, Decodable {
let url : URL
let provider : String
}
class Dummy : TypeItem {}
使用枚举正确解码type
enum Type : String, Decodable {
case text_image, video, challenge
}
在结构 Element
中,您必须实现一个自定义初始化器,它根据类型将 JSON 解码为结构。不需要的 challange
类型被解码为 Dummy
实例。由于保护伞协议,您只需要一个 属性.
class Element : Decodable {
let type : Type
let id : String
let title : String
let item : TypeItem
private enum CodingKeys : String, CodingKey {
case id, title, type, video, text_image
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
type = try container.decode(Type.self, forKey: .type)
switch type {
case .text_image: item = try container.decode(TextImage.self, forKey: .text_image)
case .video: item = try container.decode(Video.self, forKey: .video)
default: item = Dummy()
}
}
}
最后为根元素创建一个 Root
结构,为 topics
数组创建一个 Topic
结构。在 Topic
中添加一个方法来过滤 Dummy
个实例。
class Root : Decodable {
let id : String
let title : String
var topics : [Topic]
}
class Topic : Decodable {
let id : String
let title : String
var elements : [Element]
func filterDummy() {
elements = elements.filter{!([=14=].item is Dummy)}
}
}
解码后调用 filterDummy()
在每个 Topic
中删除死项。
另一个缺点是您必须将 item
转换为静态类型,例如
let result = try decoder.decode(Root.self, from: data)
result.topics.forEach({[=15=].filterDummy()})
if let videoElement = result.topics[0].elements.first(where: {[=15=].type == .video}) {
let video = videoElement.item as! Video
print(video.url)
}
我终于在 SR-5953 中找到了一些关于此的内容,但我认为这是一个 hacky。
无论如何,对于好奇的人来说,要允许这种 有损 解码,您需要手动解码所有内容。您可以将它写在 init(from decoder: Decoder)
中,但更好的方法是编写一个名为 FailableCodableArray
的新助手 struct
。实现看起来像:
struct FailableCodableArray<Element: Decodable>: Decodable {
// https://github.com/phynet/Lossy-array-decode-swift4
private struct DummyCodable: Codable {}
private struct FailableDecodable<Base: Decodable>: Decodable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.base = try? container.decode(Base.self)
}
}
private(set) var elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements = [Element]()
if let count = container.count {
elements.reserveCapacity(count)
}
while !container.isAtEnd {
guard let element = try container.decode(FailableDecodable<Element>.self).base else {
_ = try? container.decode(DummyCodable.self)
continue
}
elements.append(element)
}
self.elements = elements
}
}
而且对于那些失败元素的实际解码,您只需编写一个简单的 init(from decoder: Decoder)
实现,例如:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
elements = try container.decode(FailableCodableArray<Element>.self, forKey: .elements).elements
}
正如我所说,这个解决方案工作正常,但感觉有点 hacky。这是一个 open 错误,因此您可以对其投票并让 Swift 团队看到,像这样内置的东西会是一个很好的补充!
我一直非常高兴地在我当前的项目中使用 Codable
s - 一切都很好,我开箱即用的大部分东西都是内置的 - 完美!不过,最近我偶然发现了第一个 真正的问题 ,它无法按照我想要的方式自动解决。
问题描述
我有一个来自后端的 JSON,这是一个嵌套的东西。看起来像这样
{
"id": "fef08c8d-0b16-11e8-9e00-069b808d0ecc",
"title": "Challenge_Chapter",
"topics": [
{
"id": "5145ea2c-0b17-11e8-9e00-069b808d0ecc",
"title": "Automation_Topic",
"elements": [
{
"id": "518dfb8c-0b18-11e8-9e00-069b808d0ecc",
"title": "Automated Line examle",
"type": "text_image",
"video": null,
"challenge": null,
"text_image": {
"background_url": "",
"element_render": ""
}
},
{
"id": "002a1776-0b18-11e8-9e00-069b808d0ecc",
"title": "Industry 3.0 vs. 4.0: A vision of the new manufacturing world",
"type": "video",
"video": {
"url": "https://www.youtube.com/watch?v=xxx",
"provider": "youtube"
},
"challenge": null,
"text_image": null
},
{
"id": "272fc2b4-0b18-11e8-9e00-069b808d0ecc",
"title": "Classmarker_element",
"type": "challenge",
"video": null,
"challenge": {
"url": "https://www.classmarker.com/online-test/start/",
"description": null,
"provider": "class_marker"
},
"text_image": null
}
]
}
]
}
Chapter 是根对象,它包含一个 Topics 列表,每个主题包含一个 Elements 列表。非常简单,但我遇到了最低级别 Elements。每个 Element 都有一个来自后端的枚举,如下所示:
[ video, challenge, text_image ]
,但是 iOS 应用程序不支持挑战,所以我在 Swift 中的 ElementType 枚举看起来像:
public enum ElementType: String, Codable {
case textImage = "text_image"
case video = "video"
}
当然,它 throws
,因为发生的第一件事是它尝试解码此枚举的 challenge
值,但它不存在,所以我的整个解码都失败了。
我想要的
我只是希望解码过程 ignore Element
s 无法解码。我不需要任何 Optional
。我只是希望它们不要出现在 Topic 的元素数组中。
我的推理及其缺点
当然,我已经做了几次尝试来解决这个问题。第一个,也是最简单的一个,就是将 ElementType
标记为 Optional
,但稍后使用这种方法时,我将不得不解包所有内容并处理它——这是一项相当乏味的任务。我的第二个想法是在我的枚举中有类似 .unsupported
的情况,但是稍后我想用它来生成单元格,我将不得不 throw
或 return Optional
- 基本上,与以前的想法相同的问题。
我的最后一个想法,但我还没有测试过,是为 decodable 编写自定义 init()
并以某种方式在那里处理它,但我不确定它是 Element
还是 Topic
这应该由谁负责?如果我把它写在Element
,我不能return什么,我将不得不throw
,但如果我把它写在Topic
,我将不得不append
成功解码元素到数组。事情是如果在某个时候我将直接获取 Elements
会发生什么 - 再一次,如果没有 throw
ing 我将无法做到这一点。
TL;DR
我想要 init(from decoder: Decoder) throws
不是 throw
,而是 return Optional
.
我建议为所有三种类型创建一个伞式协议
protocol TypeItem {}
编辑:为了符合只能考虑两种类型的要求,您必须使用 classes 来获取引用语义
然后创建classes TextImage
和Video
和一个Dummy
class采用协议。 Dummy class
的所有实例都将在解码过程后删除。
class TextImage : TypeItem, Decodable {
let backgroundURL : String
let elementRender : String
private enum CodingKeys : String, CodingKey {
case backgroundURL = "background_url"
case elementRender = "element_render"
}
}
class Video : TypeItem, Decodable {
let url : URL
let provider : String
}
class Dummy : TypeItem {}
使用枚举正确解码type
enum Type : String, Decodable {
case text_image, video, challenge
}
在结构 Element
中,您必须实现一个自定义初始化器,它根据类型将 JSON 解码为结构。不需要的 challange
类型被解码为 Dummy
实例。由于保护伞协议,您只需要一个 属性.
class Element : Decodable {
let type : Type
let id : String
let title : String
let item : TypeItem
private enum CodingKeys : String, CodingKey {
case id, title, type, video, text_image
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
type = try container.decode(Type.self, forKey: .type)
switch type {
case .text_image: item = try container.decode(TextImage.self, forKey: .text_image)
case .video: item = try container.decode(Video.self, forKey: .video)
default: item = Dummy()
}
}
}
最后为根元素创建一个 Root
结构,为 topics
数组创建一个 Topic
结构。在 Topic
中添加一个方法来过滤 Dummy
个实例。
class Root : Decodable {
let id : String
let title : String
var topics : [Topic]
}
class Topic : Decodable {
let id : String
let title : String
var elements : [Element]
func filterDummy() {
elements = elements.filter{!([=14=].item is Dummy)}
}
}
解码后调用 filterDummy()
在每个 Topic
中删除死项。
另一个缺点是您必须将 item
转换为静态类型,例如
let result = try decoder.decode(Root.self, from: data)
result.topics.forEach({[=15=].filterDummy()})
if let videoElement = result.topics[0].elements.first(where: {[=15=].type == .video}) {
let video = videoElement.item as! Video
print(video.url)
}
我终于在 SR-5953 中找到了一些关于此的内容,但我认为这是一个 hacky。
无论如何,对于好奇的人来说,要允许这种 有损 解码,您需要手动解码所有内容。您可以将它写在 init(from decoder: Decoder)
中,但更好的方法是编写一个名为 FailableCodableArray
的新助手 struct
。实现看起来像:
struct FailableCodableArray<Element: Decodable>: Decodable {
// https://github.com/phynet/Lossy-array-decode-swift4
private struct DummyCodable: Codable {}
private struct FailableDecodable<Base: Decodable>: Decodable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.base = try? container.decode(Base.self)
}
}
private(set) var elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements = [Element]()
if let count = container.count {
elements.reserveCapacity(count)
}
while !container.isAtEnd {
guard let element = try container.decode(FailableDecodable<Element>.self).base else {
_ = try? container.decode(DummyCodable.self)
continue
}
elements.append(element)
}
self.elements = elements
}
}
而且对于那些失败元素的实际解码,您只需编写一个简单的 init(from decoder: Decoder)
实现,例如:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
elements = try container.decode(FailableCodableArray<Element>.self, forKey: .elements).elements
}
正如我所说,这个解决方案工作正常,但感觉有点 hacky。这是一个 open 错误,因此您可以对其投票并让 Swift 团队看到,像这样内置的东西会是一个很好的补充!