使用可编码的Swift JSON解码找到了一个字符串/数据

时间:2019-05-01 02:46:05

标签: swift codable

我正在使用《纽约时报》 API并收到消息

  

typeMismatch(Swift.Array,Swift.DecodingError.Context(codingPath:   [CodingKeys(stringValue:“ results”,intValue:nil),   _JSONKey(stringValue:“ Index 3”,intValue:3),CodingKeys(stringValue:“ multimedia”,intValue:nil)],debugDescription:“预期解码   数组,但找到了一个字符串/数据。”,底层错误:nil))

当JSON的多媒体部分是字符串而不是数组时,就会发生这种情况:类似于此SO问题:

Swift Codable expected to decode Dictionary<String, Any>but found a string/data instead

所以我决定举一个最小的例子。

附文章

public struct Article : Codable {
    var abstract: String?
    var thumbnail_standard: String?
    var multimedia: [Multimedia]?
    var title: String?
    var url: URL?

    private enum CodingKeys: String, CodingKey {
        case abstract = "abstract"
        case multimedia = "multimedia"
        case thumbnail_standard = "thumbnail_standard"
        case title = "title"
        case url = "url"
    }
}

和多媒体

struct Multimedia: Codable {
    var url: String?

    private enum CodingKeys: String, CodingKey {
        case url = "url"
    }
}

我可以使用JSON字符串

        let jsonString = """
{
      "slug_name": "30dc-emoluments",
      "section": "U.S.",
      "subsection": "Politics",
      "title": "Congressional Democrats’ Lawsuit Examining Trump’s Private Business Can Proceed, Federal Judge Says",
      "abstract": "The decision is at least a temporary victory for the president’s critics who say he is willfully flaunting constitutional bans.",
      "url": "https://www.nytimes.com/2019/04/30/us/politics/trump-emoluments-clauses.html",
      "byline": "By SHARON LaFRANIERE",
      "thumbnail_standard": "https://static01.nyt.com/images/2019/04/19/world/30dc-emoluments/30dc-emoluments-thumbStandard.jpg",
      "item_type": "Article",
      "source": "The New York Times",
      "updated_date": "2019-04-30T22:09:45-04:00",
      "created_date": "2019-04-30T21:56:05-04:00",
      "published_date": "2019-04-29T20:00:00-04:00",
      "first_published_date": "2019-04-30T21:54:34-04:00",
      "material_type_facet": "News",
      "kicker": null,
      "subheadline": null,
      "des_facet": "",
      "org_facet": [
        "Democratic Party",
        "Constitution (US)",
        "Justice Department",
        "Trump International Hotel (Washington, DC)"
      ],
      "per_facet": [
        "Sullivan, Emmet G",
        "Trump, Donald J"
      ],
      "geo_facet": "",
      "related_urls": [
        {
          "suggested_link_text": "Appeals Court Judges Appear Skeptical of Emoluments Case Against Trump",
          "url": "https://www.nytimes.com/2019/03/19/us/politics/trump-emoluments-lawsuit.html"
        },
        {
          "suggested_link_text": "Democrats in Congress Sue Trump Over Foreign Business Dealings",
          "url": "https://www.nytimes.com/2017/06/14/us/politics/democrats-in-congress-to-sue-trump-over-foreign-business-dealings.html"
        }
      ],
      "multimedia": [
        {
          "url": "https://static01.nyt.com/images/2019/04/19/world/30dc-emoluments/30dc-emoluments-thumbStandard.jpg",
          "format": "Standard Thumbnail",
          "height": 75,
          "width": 75,
          "type": "image",
          "subtype": "photo",
          "caption": "The Trump International Hotel in Washington.",
          "copyright": "Gabriella Demczuk for The New York Times"
        },
        {
          "url": "https://static01.nyt.com/images/2019/04/19/world/30dc-emoluments/30dc-emoluments-articleInline.jpg",
          "format": "Normal",
          "height": 130,
          "width": 190,
          "type": "image",
          "subtype": "photo",
          "caption": "The Trump International Hotel in Washington.",
          "copyright": "Gabriella Demczuk for The New York Times"
        },
        {
          "url": "https://static01.nyt.com/images/2019/04/19/world/30dc-emoluments/30dc-emoluments-mediumThreeByTwo210.jpg",
          "format": "mediumThreeByTwo210",
          "height": 140,
          "width": 210,
          "type": "image",
          "subtype": "photo",
          "caption": "The Trump International Hotel in Washington.",
          "copyright": "Gabriella Demczuk for The New York Times"
        },
        {
          "url": "https://static01.nyt.com/images/2019/04/19/world/30dc-emoluments/30dc-emoluments-mediumThreeByTwo440.jpg",
          "format": "mediumThreeByTwo440",
          "height": 293,
          "width": 440,
          "type": "image",
          "subtype": "photo",
          "caption": "The Trump International Hotel in Washington.",
          "copyright": "Gabriella Demczuk for The New York Times"
        }
      ]
    }
"""

只需代码即可

if let data = jsonString.data(using: .utf8)
{
    let decoder = JSONDecoder()
    let result = try? decoder.decode(Article.self, from: data)
    print(result)
}

但是以下JSON字符串被解码为nill:

        let jsonString = """
        {
            "slug_name": "01a3_quote-web",
            "section": "Today’s Paper",
            "subsection": "",
            "title": "Quotation of the Day: Who Killed Atlanta’s Children? Retesting Evidence After 40 Years",
            "abstract": "Quotation of the Day for Wednesday, May 1, 2019.",
            "url": "https://www.nytimes.com/2019/04/30/todayspaper/quotation-of-the-day-who-killed-atlantas-children-retesting-evidence-after-40-years.html",
            "byline": "",
            "thumbnail_standard": "",
            "item_type": "Article",
            "source": "The New York Times",
            "updated_date": "2019-04-30T21:26:36-04:00",
            "created_date": "2019-04-30T21:26:36-04:00",
            "published_date": "2019-04-29T20:00:00-04:00",
            "first_published_date": "2019-04-30T21:25:06-04:00",
            "material_type_facet": "Quote",
            "kicker": null,
            "subheadline": null,
            "des_facet": "",
            "org_facet": "",
            "per_facet": "",
            "geo_facet": "",
            "related_urls": null,
            "multimedia": ""
        }
"""

即使我将对象中的属性设置为可选,也使用了self。,它们都使用了encoder.decode方法。

如何获取第二个JSON字符串进行解码?

3 个答案:

答案 0 :(得分:1)

解决方案

struct Article {
    let abstract: String
    let thumbnailStandard: String
    let multimedia: [Multimedia]
    let title: String
    let url: URL
}

extension Article: Decodable {
    enum CodingKeys: String, CodingKey {
        case abstract
        case thumbnailStandard = "thumbnail_standard"
        case multimedia
        case title
        case url
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let abstract = try container.decode(String.self, forKey: .abstract)
        let thumbnailStandard = try container.decode(String.self, forKey: .thumbnailStandard)
        let multimedia = try container.decode([Multimedia].self, forKey: .multimedia)
        let title = try container.decode(String.self, forKey: .title)
        let url = try container.decode(URL.self, forKey: .url)

        self.init(abstract: abstract, thumbnailStandard: thumbnailStandard, multimedia: multimedia, title: title, url: url)
    }
}

struct Multimedia: Codable {
    let url: String
}

编辑

尝试一下

{{1}}

参考号:https://medium.com/swiftly-swift/swift-4-decodable-beyond-the-basics-990cc48b7375

答案 1 :(得分:1)

嗯,这种不一致应该由api处理。但是,您可以通过引入如下的enum来优雅地处理不同类型的返回类型,

enum MultiMediaType: Codable {

    case string(String)
    case array(Array<Multimedia>)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = .array(try container.decode([Multimedia].self))
        } catch DecodingError.typeMismatch {
            self = .string(try container.decode(String.self))
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let value):
            try container.encode(value)
        case .array(let value):
            try container.encode(value)
        }
    }
}

public struct Article : Codable {
    var abstract: String?
    var thumbnail_standard: String?
    var multimedia: MultiMediaType
    var title: String?
    var url: URL?
}

答案 2 :(得分:1)

一种解决方案是手动解码所有密钥,其值可以是不同的类型。

在此示例中,multimedia是可选的([Multimedia]nil),而perFacet是非可选的[String],如果该值为空,则为空字符串。

所有结构成员都是常量(let),并添加了convertFromSnakeCase策略以摆脱 snake_cased 名称

struct Article : Decodable {
    let abstract: String
    let thumbnailStandard: String
    let multimedia: [Multimedia]?
    let perFacet : [String]
    let title: String
    let url: URL

    private enum CodingKeys: String, CodingKey {
        case abstract, multimedia, thumbnailStandard, title, url, perFacet
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        abstract = try container.decode(String.self, forKey: .abstract)
        thumbnailStandard = try container.decode(String.self, forKey: .thumbnailStandard)
        do {
            perFacet = try container.decode([String].self, forKey: .perFacet)
        } catch DecodingError.typeMismatch {
            perFacet = []
        }
        do {
            multimedia = try container.decode([Multimedia].self, forKey: .multimedia)
        } catch DecodingError.typeMismatch {
            multimedia = nil
        }
        title = try container.decode(String.self, forKey: .title)
        url = try container.decode(URL.self, forKey: .url)
    }
}

struct Multimedia: Decodable {
    let url: URL
    let format, type, subtype, caption, copyright: String
    let height, width: Int
}

let data = Data(jsonString.utf8)

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
    let result = try decoder.decode(Article.self, from: data)
    print(result)
} catch { print(error) }