在 Swift 3 中正确解析 JSON

Correctly Parsing JSON in Swift 3

提问人:user2563039 提问时间:9/10/2016 最后编辑:Communityuser2563039 更新时间:4/6/2023 访问量:137957

问:

我正在尝试获取JSON响应并将结果存储在变量中。在以前的 Swift 版本发布之前,我已经让这个代码的版本工作了,直到 Xcode 8 的 GM 版本发布。我在 StackOverflow 上看了一些类似的帖子: Swift 2 解析 JSON - 无法在 Swift 3 中下标“AnyObject”类型的值JSON 解析

但是,那里传达的想法似乎不适用于这种情况。

如何在 Swift 3 中正确解析 JSON 响应? 在 Swift 3 中读取 JSON 的方式是否发生了一些变化?

下面是有问题的代码(它可以在 playground 中运行):

import Cocoa

let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"

if let url = NSURL(string: url) {
    if let data = try? Data(contentsOf: url as URL) {
        do {
            let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)

        //Store response in NSDictionary for easy access
        let dict = parsedData as? NSDictionary

        let currentConditions = "\(dict!["currently"]!)"

        //This produces an error, Type 'Any' has no subscript members
        let currentTemperatureF = ("\(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue

            //Display all current conditions from API
            print(currentConditions)

            //Output the current temperature in Fahrenheit
            print(currentTemperatureF)

        }
        //else throw an error detailing what went wrong
        catch let error as NSError {
            print("Details of JSON parsing error:\n \(error)")
        }
    }
}

编辑:下面是 API 调用的结果示例print(currentConditions)

["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460]
json swift 解析 swift3 xcode8

评论

0赞 User 12/3/2016
能否放置从 API 调用返回的示例数据?
1赞 user2563039 12/4/2016
是的,我只是添加了一个打印后打印的结果样本(currentConditions)。希望它有所帮助。
0赞 Naser Mohamed 4/28/2019
使用 Codable 协议在 swift4 中解析 json stackoverflow.com/a/52931265/9316566

答:

176赞 vadian 9/10/2016 #1

首先,永远不要从远程 URL 同步加载数据,始终使用异步方法,例如 .URLSession

“Any”没有下标成员

发生是因为编译器不知道中间对象是什么类型(例如,在 中),并且由于您使用的是 Foundation 集合类型,因此编译器根本不知道该类型。currently["currently"]!["temperature"]NSDictionary

此外,在 Swift 3 中,它需要通知编译器所有下标对象的类型。

您必须将 JSON 序列化的结果强制转换为实际类型。

此代码使用 Swift 本机类型URLSession

let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"

let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
  if error != nil {
    print(error)
  } else {
    do {

      let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
      let currentConditions = parsedData["currently"] as! [String:Any]

      print(currentConditions)

      let currentTemperatureF = currentConditions["temperature"] as! Double
      print(currentTemperatureF)
    } catch let error as NSError {
      print(error)
    }
  }

}.resume()

要打印所有键/值对,你可以写currentConditions

 let currentConditions = parsedData["currently"] as! [String:Any]

  for (key, value) in currentConditions {
    print("\(key) - \(value) ")
  }

关于jsonObject的说明(包含数据

许多(似乎全部)教程建议或选项在 Swift 中完全是无稽之谈。这两个选项是传统的 Objective-C 选项,用于将结果分配给对象。在 Swift 中,默认情况下,任何 iable 都是可变的,传递这些选项中的任何一个并将结果分配给常量根本没有效果。此外,大多数实现无论如何都不会改变反序列化的 JSON。.mutableContainers.mutableLeavesNSMutable...varlet

在 Swift 中唯一有用的(罕见)选项是必需的,如果 JSON 根对象可以是值类型(、 或 ) 而不是集合类型 ( 或 ) 之一。但通常省略参数,这意味着没有选项.allowFragmentsStringNumberBoolnullarraydictionaryoptions

===========================================================================

解析 JSON 的一些一般注意事项

JSON 是一种排列良好的文本格式。读取 JSON 字符串非常容易。仔细阅读字符串。只有六种不同的类型:两种集合类型和四种值类型。


集合类型包括

  • 数组 - JSON:方括号中的对象 - Swift:但在大多数情况下[][Any][[String:Any]]
  • 字典 - JSON:大括号中的对象 - Swift:{}[String:Any]

值类型为

  • 字符串 - JSON:双引号中的任何值,偶数或 – Swift:"Foo""123""false"String
  • 数字 - JSON:数值不在双引号中或 – Swift:或123123.0IntDouble
  • 布尔 - JSON:或不在双引号中 – Swift:或truefalsetruefalse
  • null - JSON: – 斯威夫特:nullNSNull

根据 JSON 规范,字典中的所有键都必须是 .String


基本上,始终建议使用可选绑定来安全地解包可选

如果根对象是字典 (),则将类型转换为{}[String:Any]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...

并使用 ( 是 JSON 集合或值类型,如上所述)按键检索值。OneOfSupportedJSONTypes

if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
    print(foo)
} 

如果根对象是数组 (),则将类型转换为[][[String:Any]]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...

并使用

for item in parsedData {
    print(item)
}

如果需要特定索引处的项目,还要检查索引是否存在

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
   let item = parsedData[2] as? OneOfSupportedJSONTypes {
      print(item)
    }
}

在极少数情况下,JSON 只是值类型之一(而不是集合类型),您必须传递选项并将结果转换为适当的值类型,例如.allowFragments

if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...

Apple 在 Swift 博客中发表了一篇综合文章:在 Swift 中使用 JSON


===========================================================================

在 Swift 4+ 中,该协议提供了一种更方便的方式将 JSON 直接解析为结构/类。Codable

例如,问题中给定的 JSON 示例(略有修改)

let jsonString = """
{"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460}
"""

可以解码成结构体。Swift 类型与上述相同。还有一些其他选项:Weather

  • 表示 的字符串可以直接解码为 。URLURL
  • 整数可以像使用 .timeDatedateDecodingStrategy.secondsSince1970
  • snaked_casedJSON 键可以通过keyDecodingStrategy .convertFromSnakeCase

struct Weather: Decodable {
    let icon, summary: String
    let pressure: Double, humidity, windSpeed : Double
    let ozone, temperature, dewPoint, cloudCover: Double
    let precipProbability, precipIntensity, apparentTemperature, windBearing : Int
    let time: Date
}

let data = Data(jsonString.utf8)
do {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let result = try decoder.decode(Weather.self, from: data)
    print(result)
} catch {
    print(error)
}

其他可编码的来源:

评论

0赞 user2563039 9/10/2016
这是非常有帮助的。我只是好奇为什么上面的代码在操场上运行时不显示可见的输出。
0赞 vadian 9/10/2016
如上所述,您需要将 的模棱两可的结果转换为字典,编译器可以安全地推断出后续的密钥订阅。dict!["currently"]!
1赞 Shades 11/23/2016
Apple 的文章很酷,但由于某种原因我无法让它与 swift 3 一起运行。它抱怨类型 [String : Any]?没有下标成员。它也有一些其他问题,但我能够绕过它。有人有实际运行的代码示例吗?
0赞 vadian 11/23/2016
@Shades 提出问题并发布您的代码。您的问题很可能与未包装的可选选项有关。
0赞 User 12/3/2016
能否放置从 API 调用返回的示例数据?
12赞 discorevilo 9/10/2016 #2

Swift 3 的 Xcode 8 Beta 6 发生的一个重大变化是 id 现在导入为 而不是 .AnyAnyObject

这意味着它作为最有可能的字典返回,类型为 。如果不使用调试器,我无法确切地告诉您强制转换将执行的操作,但是您看到的错误是因为具有类型parsedData[Any:Any]NSDictionarydict!["currently"]!Any

那么,如何解决这个问题呢?从你引用它的方式来看,我认为它是一个字典,所以你有很多选择:dict!["currently"]!

首先,你可以做这样的事情:

let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  

这将为您提供一个字典对象,然后您可以查询值,因此您可以像这样获得温度:

let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double

或者,如果您愿意,可以排队进行:

let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double

希望这会有所帮助,恐怕我没有时间编写示例应用程序来测试它。

最后一点:最简单的方法可能是在开始时直接将 JSON 有效负载转换为。[String: AnyObject]

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>

评论

0赞 vadian 9/10/2016
dict!["currently"]! as! [String: String]会崩溃
0赞 discorevilo 9/10/2016
它坠毁的点是[“温度”]!!并尝试将 String 转换为 NSString - 验证新解决方案是否有效 swiftlang.ng.bluemix.net/#/repl/57d3bc683a422409bf36c391
0赞 vadian 9/10/2016
[String: String]根本无法工作,因为有几个数值。它在 Mac Playground 中不起作用
0赞 discorevilo 9/10/2016
@vadian啊,是的,我没有注意到这些,快速沙盒帮助隐藏了它们!- 现在[再次]更正(并在macOS上进行了测试)。感谢您:)指出这一点
5赞 Marco Weber 1/5/2017 #3

更新了之后,感谢这篇文章isConnectToNetwork-Function

我为它写了一个额外的方法:

import SystemConfiguration

func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) {

    if(isConnectedToNetwork() == false){
        completionHandler("-1" as AnyObject)
        return
    }

    let request = NSMutableURLRequest(url: URL(string: link)!)
    request.httpMethod = "POST"
    request.httpBody = postString.data(using: String.Encoding.utf8)

    let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
        guard error == nil && data != nil else { // check for fundamental networking error
            print("error=\(error)")
            return
        }

        if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response)")
        }
        //JSON successfull
        do {
            let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
            DispatchQueue.main.async(execute: {
                completionHandler(parseJSON as AnyObject)
            });
        } catch let error as NSError {
            print("Failed to load: \(error.localizedDescription)")
        }
    }
    task.resume()
}

func isConnectedToNetwork() -> Bool {

    var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
    zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
    zeroAddress.sin_family = sa_family_t(AF_INET)

    let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
            SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
        }
    }

    var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
    if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
        return false
    }

    let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
    let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
    let ret = (isReachable && !needsConnection)

    return ret
}

因此,现在您可以轻松地在应用程序中随心所欲地调用它

loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in

    if(String(describing: parseJSON) == "-1"){
        print("No Internet")
    } else {

    if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool {
        //... do stuff
    }
}

评论

0赞 Kamal Panhwar 1/25/2017
Marco,在项目中添加此功能的最佳方法是什么?在任何 contorller 或模型中?
0赞 Marco Weber 2/7/2017
嘿,您只需在额外的 swift 文件或类中添加第一个方法 func loadingJSON(...)。之后,您可以从项目中的每个控制器调用它
0赞 Amr Angry 2/16/2017
我确实尝试过,但我喜欢演示完整解决方案的想法以及如何使用它,包括帮助程序方法 isConnectedToNetwork() 它让我想到了如何在好的代码中实现它的想法
0赞 Marco Weber 2/17/2017
所以我只是使用了一个新的 swift 文件(在您的项目树上左 klick,新文件 ...,swift 文件)并将其命名为 jsonhelper.swift。在此文件中放置第一个代码 loadingJSON() 和 isConnectedToNetwork()。之后,您可以在项目的每个部分使用这两个函数。例如,在 loginVC 中,作为登录按钮的一个操作,您可以使用第二个代码,显然您必须更改域、post 字符串和 paseJson 值 (parseJSON[“loginSuccessfull”]),以便它们与您的 php 文件匹配
6赞 BhuShan PaWar 1/24/2017 #4
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"

let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!

do {
    let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
    if let names = json["names"] as? [String] 
{
        print(names)
}
} catch let error as NSError {
    print("Failed to load: \(error.localizedDescription)")
}
5赞 David Siegel 10/14/2017 #5

我正是为此目的构建了 quicktype。只需粘贴示例 JSON,quicktype 就会为您的 API 数据生成以下类型层次结构:

struct Forecast {
    let hourly: Hourly
    let daily: Daily
    let currently: Currently
    let flags: Flags
    let longitude: Double
    let latitude: Double
    let offset: Int
    let timezone: String
}

struct Hourly {
    let icon: String
    let data: [Currently]
    let summary: String
}

struct Daily {
    let icon: String
    let data: [Datum]
    let summary: String
}

struct Datum {
    let precipIntensityMax: Double
    let apparentTemperatureMinTime: Int
    let apparentTemperatureLowTime: Int
    let apparentTemperatureHighTime: Int
    let apparentTemperatureHigh: Double
    let apparentTemperatureLow: Double
    let apparentTemperatureMaxTime: Int
    let apparentTemperatureMax: Double
    let apparentTemperatureMin: Double
    let icon: String
    let dewPoint: Double
    let cloudCover: Double
    let humidity: Double
    let ozone: Double
    let moonPhase: Double
    let precipIntensity: Double
    let temperatureHigh: Double
    let pressure: Double
    let precipProbability: Double
    let precipIntensityMaxTime: Int
    let precipType: String?
    let sunriseTime: Int
    let summary: String
    let sunsetTime: Int
    let temperatureMax: Double
    let time: Int
    let temperatureLow: Double
    let temperatureHighTime: Int
    let temperatureLowTime: Int
    let temperatureMin: Double
    let temperatureMaxTime: Int
    let temperatureMinTime: Int
    let uvIndexTime: Int
    let windGust: Double
    let uvIndex: Int
    let windBearing: Int
    let windGustTime: Int
    let windSpeed: Double
}

struct Currently {
    let precipProbability: Double
    let humidity: Double
    let cloudCover: Double
    let apparentTemperature: Double
    let dewPoint: Double
    let ozone: Double
    let icon: String
    let precipIntensity: Double
    let temperature: Double
    let pressure: Double
    let precipType: String?
    let summary: String
    let uvIndex: Int
    let windGust: Double
    let time: Int
    let windBearing: Int
    let windSpeed: Double
}

struct Flags {
    let sources: [String]
    let isdStations: [String]
    let units: String
}

它还生成无依赖的封送代码,以将 的返回值哄骗成 ,包括一个采用 JSON 字符串的便捷构造函数,以便您可以快速解析强类型值并访问其字段:JSONSerialization.jsonObjectForecastForecast

let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)

您可以使用 npm 安装 quicktype,或使用 Web UI 获取完整的生成代码以粘贴到您的 playground 中。npm i -g quicktype

评论

0赞 drew.. 12/31/2017
链接失败。
0赞 marko 4/29/2018
太棒了,节省时间!干得好!
1赞 Ahmet Ardal 11/18/2018
出色的工具。谢谢。
0赞 Arun K 10/27/2017 #6

问题出在 API 交互方法上。JSON 解析仅在语法上进行了更改。主要问题在于获取数据的方式。您正在使用的是获取数据的同步方式。这并非在所有情况下都有效。您应该使用的是一种异步方式来获取数据。这样,您必须通过 API 请求数据并等待它以数据响应。您可以通过 URL 会话和第三方库(如 .下面是 URL 会话方法的代码。Alamofire

let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL.init(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
    guard error == nil else {
        print(error)
    }
    do {
        let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
        // Note if your data is coming in Array you should be using [Any]()
        //Now your data is parsed in Data variable and you can use it normally
        let currentConditions = Data["currently"] as! [String:Any]
        print(currentConditions)
        let currentTemperatureF = currentConditions["temperature"] as! Double
        print(currentTemperatureF)
    } catch let error as NSError {
        print(error)
    }
}.resume()
0赞 J. Doe 1/11/2019 #7

Swift 具有强大的类型推断功能。让我们去掉 “if let” 或 “guard let” 样板,并使用函数式方法强制展开:

  1. 这是我们的 JSON。我们可以使用可选的 JSON 或普通的。我在我们的示例中使用可选:
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
  1. 帮助程序函数。我们只需要编写一次,然后与任何字典重用:
/// Curry
public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { a in
        { f(a, $0) }
    }
}

/// Function that takes key and optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? {
    return json.flatMap {
        cast($0[key])
    }
}

/// Function that takes key and return function that takes optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? {
    return curry(extract)(key)
}

/// Precedence group for our operator
precedencegroup RightApplyPrecedence {
    associativity: right
    higherThan: AssignmentPrecedence
    lowerThan: TernaryPrecedence
}

/// Apply. g § f § a === g(f(a))
infix operator § : RightApplyPrecedence
public func §<A, B>(_ f: (A) -> B, _ a: A) -> B {
    return f(a)
}

/// Wrapper around operator "as".
public func cast<A, B>(_ a: A) -> B? {
    return a as? B
}
  1. 这就是我们的魔力 - 提取价值:
let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound

只需一行代码,无需强制展开或手动铸造。此代码可在 playground 中使用,因此您可以复制并检查它。这是 GitHub 上的一个实现。

1赞 user9814840 11/17/2019 #8

这是解决问题的另一种方法。因此,请查看以下解决方案。希望对您有所帮助。

let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
    let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
    if let names = json["names"] as? [String] {
        print(names)
    }
} catch let error as NSError {
    print("Failed to load: \(error.localizedDescription)")
}
0赞 Abishek Thangaraj 3/3/2020 #9
{
    "User":[
      {
        "FirstUser":{
        "name":"John"
        },
       "Information":"XY",
        "SecondUser":{
        "name":"Tom"
      }
     }
   ]
}

如果我使用以前的json创建模型 使用此链接 [博客]:http://www.jsoncafe.com 生成可编码结构或任何格式

import Foundation
struct RootClass : Codable {
    let user : [Users]?
    enum CodingKeys: String, CodingKey {
        case user = "User"
    }

    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        user = try? values?.decodeIfPresent([Users].self, forKey: .user)
    }
}

struct Users : Codable {
    let firstUser : FirstUser?
    let information : String?
    let secondUser : SecondUser?
    enum CodingKeys: String, CodingKey {
        case firstUser = "FirstUser"
        case information = "Information"
        case secondUser = "SecondUser"
    }
    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        firstUser = try? FirstUser(from: decoder)
        information = try? values?.decodeIfPresent(String.self, forKey: .information)
        secondUser = try? SecondUser(from: decoder)
    }
}
struct SecondUser : Codable {
    let name : String?
    enum CodingKeys: String, CodingKey {
        case name = "name"
    }
    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        name = try? values?.decodeIfPresent(String.self, forKey: .name)
    }
}
struct FirstUser : Codable {
    let name : String?
    enum CodingKeys: String, CodingKey {
        case name = "name"
    }
    init(from decoder: Decoder) throws {
        let values = try? decoder.container(keyedBy: CodingKeys.self)
        name = try? values?.decodeIfPresent(String.self, forKey: .name)
    }
}

解析

    do {
        let res = try JSONDecoder().decode(RootClass.self, from: data)
        print(res?.user?.first?.firstUser?.name ?? "Yours optional value")
    } catch {
        print(error)
    }
0赞 aturan23 2/16/2021 #10

斯威夫特 5无法从您的 api 获取数据。 解析 json 的最简单方法是使用协议。或 ()。 例如:DecodableCodableEncodable & Decodable

let json = """
{
    "dueDate": {
        "year": 2021,
        "month": 2,
        "day": 17
    }
}
"""

struct WrapperModel: Codable {
    var dueDate: DueDate
}

struct DueDate: Codable {
    var year: Int
    var month: Int
    var day: Int
}

let jsonData = Data(json.utf8)

let decoder = JSONDecoder()

do {
    let model = try decoder.decode(WrapperModel.self, from: jsonData)
    print(model)
} catch {
    print(error.localizedDescription)
}
0赞 Andrew_STOP_RU_WAR_IN_UA 4/6/2023 #11

2023年 |SWIFT 5.1 版本 |结果解决方案

数据示例:

// Codable is important to be able to encode/decode from/to JSON!
struct ConfigCreds: Codable { 
    // some params
}

解决方案使用示例:

var configCreds = ConfigCreds()
var jsonStr: String = ""

// get JSON from Object
configCreds
   .asJson()
   .onSuccess { jsonStr = $0 }
   .onFailure { _ in // any failure code }

// get object of type "ConfigCreds" from JSON
someJsonString
    .decodeFromJson(type: ConfigCreds.self)
    .onSuccess { configCreds = $0 }
    .onFailure { _ in // any failure code }

后端代码:

@available(macOS 10.15, *)
public extension Encodable {
    func asJson() -> Result<String, Error>{
        JSONEncoder()
            .try(self)
            .flatMap{ $0.asString() }
    }
}

public extension String {
    func decodeFromJson<T>(type: T.Type) -> Result<T, Error> where T: Decodable {
        self.asData()
            .flatMap { JSONDecoder().try(type, from: $0) }
    }
}

///////////////////////////////
/// HELPERS
//////////////////////////////

@available(macOS 10.15, *)
fileprivate extension JSONEncoder {
    func `try`<T : Encodable>(_ value: T) -> Result<Output, Error> {
        do {
            return .success(try self.encode(value))
        } catch {
            return .failure(error)
        }
    }
}

fileprivate extension JSONDecoder {
    func `try`<T: Decodable>(_ t: T.Type, from data: Data) -> Result<T,Error> {
        do {
            return .success(try self.decode(t, from: data))
        } catch {
            return .failure(error)
        }
    }
}

fileprivate extension String {
    func asData() -> Result<Data, Error> {
        if let data = self.data(using: .utf8) {
            return .success(data)
        } else {
            return .failure(WTF("can't convert string to data: \(self)"))
        }
    }
}

fileprivate extension Data {
    func asString() -> Result<String, Error> {
        if let str = String(data: self, encoding: .utf8) {
            return .success(str)
        } else {
            return .failure(WTF("can't convert Data to string"))
        }
    }
}

fileprivate func WTF(_ msg: String, code: Int = 0) -> Error {
    NSError(code: code, message: msg)
}

internal extension NSError {
    convenience init(code: Int, message: String) {
        let userInfo: [String: String] = [NSLocalizedDescriptionKey:message]
        self.init(domain: "FTW", code: code, userInfo: userInfo)
    }
}