简体   繁体   English

使用可选字段解码 json

[英]Decoding json with optional fields

I am trying to parse a json string in which some of the keys are not fixed.我正在尝试解析一个 json 字符串,其中一些键未固定。 There are some keys which are related to error and either it will be error or the result data.有一些与错误相关的键,要么是错误,要么是结果数据。 Followings are the two examples:以下是两个例子:

{
    "ok": true,
    "result": {
        "code": "694kyH",
        "short_link": "shrtco.de\/694kyH",
        "full_short_link": "https:\/\/shrtco.de\/694kyH",
        "short_link2": "9qr.de\/694kyH",
        "full_short_link2": "https:\/\/9qr.de\/694kyH",
        "short_link3": "shiny.link\/694kyH",
        "full_short_link3": "https:\/\/shiny.link\/694kyH",
        "share_link": "shrtco.de\/share\/694kyH",
        "full_share_link": "https:\/\/shrtco.de\/share\/694kyH",
        "original_link": "http:\/\/google.com"
    }
}

{
    "ok": false,
    "error_code": 2,
    "error": "This is not a valid URL, for more infos see shrtco.de\/docs"
}

How will I parse this JSON. I have tried to build my class like following but it is not working:我将如何解析这个 JSON。我尝试像下面这样构建我的 class 但它不起作用:

struct ShortLinkData: Codable {
    let ok: Bool
    let result: Result?
    let errorCode: Int?
    let error: String?
    
    private enum CodingKeys : String, CodingKey { case ok, result, errorCode = "error_code", error }

       init(from decoder: Decoder) throws {
          let container = try decoder.container(keyedBy: CodingKeys.self)
          ok = try container.decode(Bool.self, forKey: .ok)
           result = try container.decode(Result.self, forKey: .result)
           errorCode = try container.decodeIfPresent(Int.self, forKey: .errorCode)
           error = try container.decodeIfPresent(String.self, forKey: .error)
      }
}

// MARK: - Result
struct Result: Codable {
    let code, shortLink: String
    let fullShortLink: String
    let shortLink2: String
    let fullShortLink2: String
    let shortLink3: String
    let fullShortLink3: String
    let shareLink: String
    let fullShareLink: String
    let originalLink: String

    enum CodingKeys: String, CodingKey {
        case code
        case shortLink = "short_link"
        case fullShortLink = "full_short_link"
        case shortLink2 = "short_link2"
        case fullShortLink2 = "full_short_link2"
        case shortLink3 = "short_link3"
        case fullShortLink3 = "full_short_link3"
        case shareLink = "share_link"
        case fullShareLink = "full_share_link"
        case originalLink = "original_link"
    }
    init(from decoder: Decoder) throws {
       let container = try decoder.container(keyedBy: CodingKeys.self)
        code = try container.decode(String.self, forKey: .code)
        shortLink = try container.decode(String.self, forKey: .shortLink)
        fullShortLink = try container.decode(String.self, forKey: .fullShortLink)
        shortLink2 = try container.decode(String.self, forKey: .shortLink2)
        fullShortLink2 = try container.decode(String.self, forKey: .fullShortLink2)
        shortLink3 = try container.decode(String.self, forKey: .shortLink3)
        fullShortLink3 = try container.decode(String.self, forKey: .fullShortLink3)
        shareLink = try container.decode(String.self, forKey: .shareLink)
        fullShareLink = try container.decode(String.self, forKey: .fullShareLink)
        originalLink = try container.decode(String.self, forKey: .originalLink)
   }
}

My parsing code:我的解析代码:

let str = String(decoding: data, as: UTF8.self)
print(str)
let shortURL = try? JSONDecoder().decode(ShortLinkData.self, from: data)
return shortURL!

I am always getting nil in shortURL object.我总是在 shortURL object 中得到 nil。

You should split this into several steps in order to avoid to handle all these optionals in your model.你应该把它分成几个步骤,以避免在你的 model 中处理所有这些选项。

First create a struct that has only those properties that are guaranteed to be there.首先创建一个结构,它只包含那些保证存在的属性。 ok in your case: ok ,在你的情况下:

struct OKResult: Codable{
    let ok: Bool
}

then create one for your error state and one for your success state:然后为您的错误创建一个 state,为您的成功创建一个 state:

struct ErrorResult: Codable{
    let ok: Bool
    let errorCode: Int
    let error: String
    
    private enum CodingKeys: String, CodingKey{
        case ok, errorCode = "error_code", error
    }
}

struct ShortLinkData: Codable {
    let ok: Bool
    let result: Result
}

struct Result: Codable {
    let code, shortLink: String
    let fullShortLink: String
    let shortLink2: String
    let fullShortLink2: String
    let shortLink3: String
    let fullShortLink3: String
    let shareLink: String
    let fullShareLink: String
    let originalLink: String

    enum CodingKeys: String, CodingKey {
        case code
        case shortLink = "short_link"
        case fullShortLink = "full_short_link"
        case shortLink2 = "short_link2"
        case fullShortLink2 = "full_short_link2"
        case shortLink3 = "short_link3"
        case fullShortLink3 = "full_short_link3"
        case shareLink = "share_link"
        case fullShareLink = "full_share_link"
        case originalLink = "original_link"
    }
}

Then you can decode the data:然后你可以解码数据:

guard try JSONDecoder().decode(OKResult.self, from: data).ok else{
    let errorResponse = try JSONDecoder().decode(ErrorResult.self, from: data)
    //handle error scenario
    fatalError(errorResponse.error) // or throw custom error or return nil etc...
}

let shortlinkData = try JSONDecoder().decode(ShortLinkData.self, from: data)

Remarks:评论:

  • Your init s are not necessary.您的init不是必需的。
  • Never use try?从不使用try? this will hide all errors from you这将对您隐藏所有错误
  • you would need to wrap this either in a do catch block or make your function throwing and handle errors further up the tree.您需要将它包装在一个do catch块中,或者让您的 function throwing错误并在树上进一步处理错误。

Actually there are no optional fields .实际上没有可选字段 The server sends two different but distinct JSON strings.服务器发送两个不同但不同的 JSON 字符串。

A suitable way to decode both JSON strings is an enum with associated values.解码两个 JSON 字符串的合适方法是使用具有关联值的枚举。 It decodes the ok key, then it decodes either the result dictionary or errorCode and error它解码ok键,然后解码result字典或errorCodeerror

enum Response : Decodable {
    case success(ShortLinkData), failure(Int, String)
    
    private enum CodingKeys : String, CodingKey { case ok, result, errorCode, error }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let ok = try container.decode(Bool.self, forKey: .ok)
        if ok {
            let result = try container.decode(ShortLinkData.self, forKey: .result)
            self = .success(result)
        } else {
            let errorCode = try container.decode(Int.self, forKey: .errorCode)
            let error = try container.decode(String.self, forKey: .error)
            self = .failure(errorCode, error)
        }
    }
}

In ShortLinkData the init method and the CodingKeys are redundant if you specify the convertFromSnakeCase key decoding strategyShortLinkData中,如果您指定convertFromSnakeCase密钥解码策略,则init方法和CodingKeys是多余的

struct ShortLinkData: Decodable {
    let code, shortLink: String
    let fullShortLink: String
    let shortLink2, fullShortLink2: String
    let shortLink3, fullShortLink3: String
    let shareLink, fullShareLink: String
    let originalLink: String
}


do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let result = try decoder.decode(Response.self, from: data)
    switch result {
        case .success(let linkData): print(linkData)
        case .failure(let code, let message): print("An error occurred with code \(code) and message \(message)")
    }
} catch {
    print(error)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM