我一直在我当前的项目中使用Codable
非常愉快 - 一切都很好,我开箱即用的大部分内容都是完美的!虽然,最近我偶然发现了第一个真正的问题,它无法按照我想要的方式自动解决。
问题描述
我有一个来自后端的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
}
]
}
]
}
章是根对象,它包含主题列表,每个主题都包含元素列表。非常简单,但我遇到了最低级别,元素。每个元素都有一个来自后端的枚举,如下所示:
[ video, challenge, text_image ]
,但iOS应用不支持质询,因此我在Swift中的 ElementType 枚举如下所示:
public enum ElementType: String, Codable {
case textImage = "text_image"
case video = "video"
}
当然,它是throws
,因为发生的第一件事是它试图解码此枚举的challenge
值并且它不存在,所以我的整个解码失败。
我想要什么
我只想将解码过程转移到无法解码的忽略 Element
。我不需要任何Optional
个。我只是希望它们不会出现在Topic的Elements数组中。
我的推理及其缺点
当然,我已经做了几次尝试来解决这个问题。第一个,简单的一个就是将ElementType
标记为Optional
,但是稍后使用这种方法,我将不得不打开所有内容并处理它 - 这是一项相当繁琐的任务。我的第二个想法是在我的枚举中有类似.unsupported
的情况,但是后来我想用它来生成单元格,我将不得不throw
或返回Optional
- 基本上,和以前的想法一样的问题。
我的最后一个想法,但我还没有测试过,就是为可解码编写一个自定义的init()
并以某种方式处理它,但我不确定它是Element
还是{{1}应该对此负责?如果我在Topic
中写下来,我就不会返回任何内容,我必须Element
,但如果我将其放入throw
,我将不得不Topic
成功将元素解码为数组。如果在某些时候我将直接获取append
,那么将会发生这种情况 - 如果没有Elements
,我将无法再这样做。
TL; DR
我希望throw
不要init(from decoder: Decoder) throws
,而是返回throw
。
答案 0 :(得分:1)
我建议为所有三种类型创建一个伞形协议
protocol TypeItem {}
编辑:为了符合只考虑两种类型的要求,您必须使用类来获取引用语义
然后创建类TextImage
和Video
以及采用该协议的Dummy
类。解码过程后,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
}
在struct 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
结构,为Topic
数组创建topics
结构。在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{!($0.item is Dummy)}
}
}
在每个filterDummy()
中解码后调用Topic
以删除死项。
另一个缺点是你必须将item
转换为静态类型,例如
let result = try decoder.decode(Root.self, from: data)
result.topics.forEach({$0.filterDummy()})
if let videoElement = result.topics[0].elements.first(where: {$0.type == .video}) {
let video = videoElement.item as! Video
print(video.url)
}
答案 1 :(得分:1)
我终于在SR-5953找到了一些相关内容,但我认为这是一个hacky。
无论如何,对于那些允许这种有损解码的好奇者,您需要手动解码所有内容。您可以在init(from decoder: Decoder)
中编写它,但更好的方法是编写一个名为struct
的新帮助程序FailableCodableArray
。实施看起来像:
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 。这是一个开放错误,所以你可以投票给它,让Swift团队看到,内置的内容将是一个很好的补充!