简体   繁体   English

JSON解析Swift。当value不为nil时,我可以解析JSON数据。 如果值为 nil,我将收到一个空数组而不是 nil

[英]JSON parsing in Swift. When value is not nil, I can parse the JSON data. If value is nil, I receive an empty array instead of nil

So, let me detail a bit with an example:所以,让我用一个例子详细说明一下:

Lets say I want to get data regarding Persons.假设我想获取有关人员的数据。 Every person has 3 collections: Friends, Family and Work.每个人都有 3 个 collections:朋友、家人和工作。

Friends collection contains: Person objects(which describes every friend: name age aso) Friends 集合包含:Person 对象(描述每个朋友:name age aso)

Family collection contains: Person objects(which describes every family member: name age aso) Family 集合包含:Person 对象(描述每个家庭成员:name age aso)

Work collection contains: Person objects(which describes every work partner: name age aso)工作集合包含:Person 对象(它描述了每个工作伙伴:name age aso)

So it will be something like this:所以它会是这样的:

Me {

age:25,

name: Dan,

Friends [{age:21,name:Andrew}, {age:30,name:Mars}]

Family [{age:21,name:Andrew}, {age:30,name:Mars}]

Work [{age:21,name:Andrew}, {age:30,name:Mars}]

}

Now, having this example in mind.现在,记住这个例子。 If I don't want to specify my name, the instead of "age:25" I will have "age:nil", right?如果我不想指定我的名字,我将使用“age:nil”而不是“age:25”,对吗? Because age is an Int?, an obtional.因为 age 是一个 Int?,一个可选的。

My problem is, when I parse the JSON data, instead of receiving a nil value (from that obtional age:Int?), I receive an empty array of type [Int], so inteased of nil, I receive [].我的问题是,当我解析 JSON 数据时,我没有收到 nil 值(来自那个 obtional age:Int?),而是收到了一个类型为 [Int] 的空数组,所以我收到了 []。

Now, this is my example:现在,这是我的例子:

class FinancialData: Codable {
    
    var maxAge:Int?
    var currentPrice:CurrentPrice?
    var targetHighPrice:TargetHighPrice?
    var targetLowPrice:TargetLowPrice?
    var targetMeanPrice:TargetMeanPrice?
    var targetMedianPrice:TargetMedianPrice?
    var recommendationMean:RecommendationMean?
    var recommendationKey, financialCurrency:String?
    var numberOfAnalystOpinions:NumberOfAnalystOpinions?
    var totalCash:TotalCash?
    var totalCashPerShare:TotalCashPerShare?
    var ebitda:Ebitda
    var totalDebt:TotalDebt?
    var quickRatio:QuickRatio?
    var currentRatio:CurrentRatio?
    var totalRevenue:TotalRevenue?
    var debtToEquity:DebtToEquity?
    var revenuePerShare:RevenuePerShare?
    var returnOnAssets:ReturnOnAssets?
    var returnOnEquity:ReturnOnEquity?
    var grossProfits:GrossProfits?
    var freeCashflow:FreeCashFlow?
    var operatingCashflow:OperatingCashFlow?
//    var earningsGrowth:EarningsGrowth?
    var revenueGrowth:RevenueGrowth?
    var grossMargins:GrossMargins?
    var ebitdaMargins:EbitdaMargins?
    var operatingMargins:OperatingMargins?
    var profitMargins:ProfitMargins?
    
    enum CodingKeys: String, CodingKey {
        
        case maxAge = "maxAge"
        case currentPrice = "currentPrice"
        case targetHighPrice = "targetHighPrice"
        case targetLowPrice = "targetLowPrice"
        case targetMeanPrice = "targetMeanPrice"
        case targetMedianPrice = "targetMedianPrice"
        case recommendationMean = "recommendationMean"
        case recommendationKey = "recommendationKey"
        case numberOfAnalystOpinions = "numberOfAnalystOpinions"
        case totalCash = "totalCash"
        case totalCashPerShare = "totalCashPerShare"
        case ebitda = "ebitda"
        case totalDebt = "totalDebt"
        case quickRatio = "quickRatio"
        case currentRatio = "currentRatio"
        case totalRevenue = "totalRevenue"
        case debtToEquity = "debtToEquity"
        case revenuePerShare = "revenuePerShare"
        case returnOnAssets = "returnOnAssets"
        case returnOnEquity = "returnOnEquity"
        case grossProfits = "grossProfits"
        case freeCashflow = "freeCashflow"
        case operatingCashflow = "operatingCashflow"
     //   case earningsGrowth = "earningsGrowth"
        case revenueGrowth = "revenueGrowth"
        case grossMargins = "grossMargins"
        case ebitdaMargins = "ebitdaMargins"
        case operatingMargins = "operatingMargins"
        case profitMargins = "profitMargins"
        case financialCurrency = "financialCurrency"
    }
}

//....//

class Ebitda: Codable {
    
    var raw:Double?
    var fmt, longFmt: String?
    
    enum CodingKeys: String, CodingKey {
        
        case raw = "raw"
        case fmt = "fmt"
        case longFmt = "longFmt"
    }
}

Ebitda is the problem, is a class. If there is no Ebitda class at my API, I am not receiving an Obtional nil value of Ebitda?, I am receiving an empty array of Ebitda, and my program crashes. Ebitda 是问题所在,是一个 class。如果我的 API 没有 Ebitda class,我没有收到 Ebitda 的 Obtional nil 值?,我收到一个空的 Ebitda 数组,我的程序崩溃了。 I can't modify Ebitda?我不能修改 Ebitda? to [Ebitda] because when the value is NOT nil, I receive an Ebitda object, not an array of Ebitda objects.到 [Ebitda] 因为当值不为零时,我收到一个 Ebitda object,而不是 Ebitda 对象数组。

So, this is the error when Ebitda is nil, but I receive an empty array instead of nil:所以,这是 Ebitda 为零时的错误,但我收到一个空数组而不是零:

typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "financialData", intValue: nil), CodingKeys(stringValue: "ebitda", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)). typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "financialData", intValue: nil), CodingKeys(stringValue: "ebitda", intValue: nil)], debugDescription :“预期解码 Dictionary<String, Any> 但发现了一个数组。”, underlyingError: nil))。

I will attach a short description of the JSON data I import, Look for ebitda:我将附上我导入的 JSON 数据的简短描述,寻找 ebitda:

{
    
    
    "financialData": {
        "maxAge": 86400,
        "recommendationKey": "buy",
        "numberOfAnalystOpinions": {
            "raw": 23,
            "fmt": "23",
            "longFmt": "23"
        },
        "totalCash": {
            "raw": 848978968576,
            "fmt": "848.98B",
            "longFmt": "848,978,968,576"
        },
        "totalCashPerShare": {
            "raw": 106.165,
            "fmt": "106.17"
        },
        "ebitda": [],
        "totalDebt": {
            "raw": 543510003712,
            "fmt": "543.51B",
            "longFmt": "543,510,003,712"
        },
        "quickRatio": [],
        "currentRatio": [],
        "debtToEquity": [],
        "revenuePerShare": {
            "raw": 11.389,
            "fmt": "11.39"
        },
        "returnOnAssets": {
            "raw": 0.00885,
            "fmt": "0.88%"
        },
        "returnOnEquity": {
            "raw": 0.101339996,
            "fmt": "10.13%"
        },
        "grossProfits": {
            "raw": 92407000000,
            "fmt": "92.41B",
            "longFmt": "92,407,000,000"
        },
        "freeCashflow": [],
        "operatingCashflow": [],
        "earningsGrowth": {
            "raw": 0.069,
            "fmt": "6.90%"
        },
        "revenueGrowth": {
            "raw": 0.04,
            "fmt": "4.00%"
        },
        "grossMargins": {
            "raw": 0,
            "fmt": "0.00%"
        },
        "ebitdaMargins": {
            "raw": 0,
            "fmt": "0.00%"
        },
        "operatingMargins": {
            "raw": 0.33514,
            "fmt": "33.51%"
        },
        "profitMargins": {
            "raw": 0.29790002,
            "fmt": "29.79%"
        },
        "financialCurrency": "USD"
    }
    
}

As you can see, also quickRatio and currentRatio are empty arrays of no type instead of CurrentRatio object nil.如您所见,quickRatio 和 currentRatio 也为无类型的空 arrays 而不是 CurrentRatio object nil。 What can I do to solve this issue?我能做些什么来解决这个问题? Thank you!谢谢你!

I was thinking about computed properties, but it doesn't work.我在考虑计算属性,但它不起作用。 Is there any way to detect when parsing the data if the value is nil or not before attributing it to my variable?在将其归因于我的变量之前,是否有任何方法可以检测解析数据时是否为 nil? If I can do that, I will be able (for ebitda and similar cases):如果我能做到这一点,我将能够(对于 ebitda 和类似情况):

If the value is not nil (in my case is not an empty array) to atribute to ebitda variable an Ebitda object from the json data.如果该值不为零(在我的例子中不是空数组),则将 json 数据中的 Ebitda object 归因于 ebitda 变量。

If the value is nil(in my case an empty array of no type) to atribute to ebitda value a nil value of Ebitda?如果该值为 nil(在我的例子中是一个无类型的空数组),则将 Ebitda 的 nil 值归因于 ebitda 值? object. object。

I think that is the solution, I can't atribute to ebitda:Ebitda?我认为这是解决方案,我不能归因于 ebitda:Ebitda? an array.. I have to atribute a nil value of Ebitda?...but how can I detect that before atributing the value?一个数组.. 我必须归因于 Ebitda 的零值?...但是我如何在归因值之前检测到它?

Thank you and I am here at any hour!谢谢你,我随时都在这里!

I have seen this issue before.我以前见过这个问题。 I believe this is due to JS backend, which sometimes (and I don't really know why) sends empty array instead of the null. To distill your problem in simpler case: seems that you may get 2 different values for the same field:我相信这是由于 JS 后端,有时(我真的不知道为什么)发送空数组而不是 null。为了在更简单的情况下提炼您的问题:似乎您可能会为同一字段获得 2 个不同的值:

let json1 = """
{
   "age": 2
}
""".data(using: .utf8)!

or, if value is nil , you are getting an empty array:或者,如果值为nil ,您将得到一个空数组:

let json2 = """
{
   "age": []
}
""".data(using: .utf8)!

So if you just define your age as Int :因此,如果您将age定义为Int

struct Obj: Codable {
    let age: Int?
}

it will work for json1 , but will crash for json2 .它适用于json1 ,但会崩溃json2

Unfortunately it means you need to switch to "manually parse" such field, and because of it, the entire structure:不幸的是,这意味着您需要切换到“手动解析”这样的字段,因此,整个结构:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: Keys.self)
    age = try? container.decode(Int.self, forKey: .age)
    // parse every field in the structure
}

But if you have many fields in your struct, it becomes very tedious.但是如果你的结构中有很多字段,它就会变得非常乏味。 So a bit of a shortcut is to define a structure that parses it for you:所以一个捷径是定义一个为你解析它的结构:

struct ValueOrEmptyArray<T: Codable>: Codable {
    
    let value: T?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            value = try container.decode(T.self) // try to parse
        } catch {
            value = nil // failed parsing - assume it's nil
        }
    }
}

And so you can use it in your struct without doing parsing for each and every field:因此,您可以在结构中使用它,而无需对每个字段进行解析:

struct Obj: Codable {
    let age: ValueOrEmptyArray<Int>
}

Of course it also means that to access a value of such field, you need to access age.value :当然这也意味着要访问此类字段的值,您需要访问age.value

let decoded1 = try JSONDecoder().decode(Obj.self, from: json1)
print(decoded1.age.value) // 2
let decoded2 = try JSONDecoder().decode(Obj.self, from: json2) // throws an exception
print(decoded2.age.value) // nil

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

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