格式错误的 JSON 条目有时是一个数组有时是一个字典

Malformed JSON entry sometimes is an array sometimes a dictionary

我正在尝试解析 curl "https://blockchain.info/block-height/699999"

的结果

在其结果中,它 returns spending_outpoints 作为字典数组,但有时它使用空字典 "spending_outpoints" = { } 而不是数组 "spending_outpoints" = [ ... ]。我已经实施了一个解决方法,但这也因以下错误而失败:

AppVisualizer/AppVisualizerApp.swift:71: Fatal error: 'try!' 
expression unexpectedly raised an error: 
Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, 
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: 
"blocks", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 
0), CodingKeys(stringValue: "tx", intValue: nil), 
_JSONKey(stringValue: "Index 0", intValue: 0), 
CodingKeys(stringValue: "inputs", intValue: nil), 
_JSONKey(stringValue: "Index 0", intValue: 0), 
CodingKeys(stringValue: "prev_out", intValue: nil), 
CodingKeys(stringValue: "spending_outpoints", intValue: nil)], 
debugDescription: "Expected to decode Dictionary<String, Any> but 
found an array instead.", underlyingError: nil))

解决方法是枚举 SpendingOutpoints,它可以是数组或字典。由于字典应为空,因此值类型应该无关紧要。

我的相关编码结构是:

        struct SpendingOutpoint : Codable {
            var tx_index            : UInt64
            var n                   : UInt32
        }
        enum SpendingOutpoints : Codable {
            case array(Array<SpendingOutpoint>)
            case dictionary(Dictionary<String,String>)
            init(from decoder: Decoder) throws {
                let container = try decoder.container(keyedBy: CodingKeys.self)
                if let array = try? container.decode(Array<SpendingOutpoint>.self, forKey: .array) {
                    self = .array(array)
                    return
                }
                if let dictionary = try? container.decode(Dictionary<String,String>.self, forKey: .dictionary) {
                    self = .dictionary(dictionary)
                    return
                }
                throw DecodingError.typeMismatch(SpendingOutpoints.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unexptected type: expecting array or dictionary"))
            }
        }
        struct Output : Codable {
            var type                : Int
            var spent               : Bool
            var script              : String
            var value               : UInt64
            var spending_outpoints  : SpendingOutpoints
            var tx_index            : UInt64
            var n                   : UInt64
            var addr                : String?
        }
let url = Bundle.main.url(forResource: "blockheight", withExtension: "json")!
let data1 = try! JSONDecoder().decode(API.Blockchain.Blockheight.self, from: Data.init(contentsOf: url))

完整的Codable结构集:

struct API {
    struct Blockchain {
        enum SpendingOutpoints : Codable {
            case array(Array<SpendingOutpoint>)
            case dictionary(Dictionary<String,String>)
            init(from decoder: Decoder) throws {
                let container = try decoder.container(keyedBy: CodingKeys.self)
                if let array = try? container.decode(Array<SpendingOutpoint>.self, forKey: .array) {
                    self = .array(array)
                    return
                }
                if let dictionary = try? container.decode(Dictionary<String,String>.self, forKey: .dictionary) {
                    self = .dictionary(dictionary)
                    return
                }
                throw DecodingError.typeMismatch(SpendingOutpoints.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unexptected type: expecting array or dictionary"))
            }
        }
        struct SpendingOutpoint : Codable {
                //            {
                //            "tx_index":5602502841772452,
                //            "n":0
                //            }
            var tx_index            : UInt64
            var n                   : UInt32
        }
        struct Output : Codable {
                //            "type":0,
                //            "spent":true,
                //            "value":626178339,
                //            "spending_outpoints":[
                //                {
                //                    "tx_index":4405887896017968,
                //                    "n":2
                //                }
                //            ],
                //            "n":0,
                //            "tx_index":4989293645553533,
                //            "script":"76a91474e878616bd5e5236ecb22667627eeecbff54b9f88ac",
                //            "addr":"1Bf9sZvBHPFGVPX71WX2njhd1NXKv5y7v5"
            var type                : Int
            var spent               : Bool
            var script              : String
            var value               : UInt64
            var spending_outpoints  : SpendingOutpoints
            var tx_index            : UInt64
            var n                   : UInt64
            var addr                : String?
        }
        struct Input : Codable {
                //            "sequence":4294967295,
                //            "witness":"01200000000000000000000000000000000000000000000000000000000000000000",
                //            "script":"03c9f00a1362696e616e63652f3831305000fb0162c56408fabe6d6d85e315917dcf79bdee12fcf0b412ca00bdbe3ec986dec2a30884d4b5ff512d1a0200000000000000a4aa0000ec190200",
                //            "index":0,
                //            "prev_out":{
            var sequence            : UInt64
            var witness             : String
            var script              : String
            var index               : UInt32
            var prev_out            : Output
        }
        struct Tx : Codable {
                //            "hash":"f2b8c6f1586e238d9f56e16fdfe8d31e5c086be5889cb7dbcf244de5bd923b9f",
                //            "ver":1,
                //            "vin_sz":1,
                //            "vout_sz":4,
                //            "size":343,
                //            "weight":1264,
                //            "fee":0,
                //            "relayed_by":"0.0.0.0",
                //            "lock_time":0,
                //            "tx_index":5602502841772452,
                //            "double_spend":false,
                //            "time":1641209474,
                //            "block_index":717001,
                //            "block_height":717001,
                //            "inputs":[
            var hash            : String
            var ver             : Int
            var vin_sz          : Int
            var vout_sz         : Int
            var size            : Int
            var weight          : Int
            var fee             : Double
            var relayed_by      : String
            var lock_time       : UInt32
            var tx_index        : UInt64
            var double_spend    : Bool
            var time            : UInt32
            var block_index     : UInt32
            var block_height    : UInt32
            var inputs          : [Input]
            var out             : [Output]
        }
        struct Block : Codable {
                //            "hash":"00000000000000000003d03aab20439e69f319e847f101ccd8a7cfbef9473561",
                //            "ver":545259520,
                //            "prev_block":"00000000000000000009396fbe5531a11f90bae421aa1621def645e0fe5e4e1b",
                //            "mrkl_root":"f2b8c6f1586e238d9f56e16fdfe8d31e5c086be5889cb7dbcf244de5bd923b9f",
                //            "time":1641209474,
                //            "bits":386635947,
                //            "next_block":[
                //            "0000000000000000000b6713adeb66d64cacefbcc5b402db608f75d6edd0f2cf"
                //            ],
                //            "fee":0,
                //            "nonce":3212120200,
                //            "n_tx":1,
                //            "size":424,
                //            "block_index":717001,
                //            "main_chain":true,
                //            "height":717001,
                //            "weight":1588,
                //            "tx":[
            
            var hash        : String
            var ver         : UInt64
            var prev_block  : String
            var mrkl_root   : String
            var time        : UInt64
            var bits        : UInt64
            var next_block  : [String]
            var fee         : UInt64
            var nonce       : UInt32
            var n_tx        : Int
            var size        : Int
            var block_index : UInt
            var main_chain  : Bool
            var height      : UInt32
            var weight      : UInt32
            var tx          : [Tx]
        }
        struct Blockheight : Codable {
            var blocks : [Block]
        }
    }
}

这是 json 文件:

{
    "blocks":[
        {
            "hash":"0000000000000000000aa3ce000eb559f4143be419108134e0ce71042fc636eb",
            "ver":832872452,
            "prev_block":"00000000000000000007a60f43d15d097bd369dd8c6c53f12b32f649f979e85e",
            "mrkl_root":"a961ea5c3420ca74d28e33e7dfe5e871da6db36ee63d62687e791a9f3098596d",
            "time":1631332753,
            "bits":386877668,
            "next_block":[
                "0000000000000000000590fc0f3eba193a278534220b2b37e9849e1a770ca959"
            ],
            "fee":1178339,
            "nonce":648207636,
            "n_tx":292,
            "size":141595,
            "block_index":699999,
            "main_chain":true,
            "height":699999,
            "weight":372724,
            "tx":[
                {
                    "hash":"3a7e5833d940a75fc542364d4eba8e3ea37b84f4c061b35c3ceafb629be3cd8d",
                    "ver":2,
                    "vin_sz":1,
                    "vout_sz":3,
                    "size":293,
                    "weight":1064,
                    "fee":0,
                    "relayed_by":"0.0.0.0",
                    "lock_time":0,
                    "tx_index":4989293645553533,
                    "double_spend":false,
                    "time":1631332753,
                    "block_index":699999,
                    "block_height":699999,
                    "inputs":[
                        {
                            "sequence":4294967295,
                            "witness":"01200000000000000000000000000000000000000000000000000000000000000000",
                            "script":"035fae0a0413789b644254432e636f6d2f7563386575fabe6d6da8a2adde9c18f88da1fd1c3c529ce4165f9e4b2f87164bcb9f1a7a07b0c89dce020000008e9b20aa2058499a0000000000000000",
                            "index":0,
                            "prev_out":{
                                "spent":true,
                                "script":"",
                                "spending_outpoints":[
                                    {
                                        "tx_index":4989293645553533,
                                        "n":0
                                    }
                                ],
                                "tx_index":0,
                                "value":0,
                                "n":4294967295,
                                "type":0
                            }
                        }
                    ],
                    "out":[
                        {
                            "type":0,
                            "spent":true,
                            "value":626178339,
                            "spending_outpoints":[
                                {
                                    "tx_index":4405887896017968,
                                    "n":2
                                }
                            ],
                            "n":0,
                            "tx_index":4989293645553533,
                            "script":"76a91474e878616bd5e5236ecb22667627eeecbff54b9f88ac",
                            "addr":"1Bf9sZvBHPFGVPX71WX2njhd1NXKv5y7v5"
                        },
                        {
                            "type":0,
                            "spent":false,
                            "value":0,
                            "spending_outpoints":{
                            },
                            "n":1,
                            "tx_index":4989293645553533,
                            "script":"6a24aa21a9ed2f5cb87274312f2e7f785e34be6db44afec9d1263eb0977b6e3c8c7f7df6ec7d"
                        },
                        {
                            "type":0,
                            "spent":false,
                            "value":0,
                            "spending_outpoints":{
                            },
                            "n":2,
                            "tx_index":4989293645553533,
                            "script":"6a24b9e11b6de2202207ad664f985557d8de23adafd23c36fc708614dd2220b58e735dfd0ca9"
                        }
                    ]
                }
            ]
        }
    ]
}

这是一个可行的解决方案。为了演示,从样本 JSON 中删除所有数据就足够了,spending_outpoints 和周围的数据结构除外。

let json = """
[
    {
        "spending_outpoints": [{
            "tx_index": 4405887896017968,
            "n": 2
        }]
    },
    {
        "spending_outpoints": {}
    },
    {
        "spending_outpoints": {}
    }
]
"""

这是我们获取字典数组时的字典结构:

struct Outpoint: Decodable {
    let tx_index: Int
    let n: Int
}

这是接受数组或(空)字典的联合枚举:

enum ArrayOrDict: Decodable {
    case array(Array<Outpoint>)
    case dict(Dictionary<String,String>)
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let array = try? container.decode(Array<Outpoint>.self) {
            self = .array(array)
            return
        }
        if let dict = try? container.decode(Dictionary<String,String>.self) {
            self = .dict(dict)
            return
        }
        throw DecodingError.typeMismatch(ArrayOrDict.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Default"))
    }
}

整体字典类型如下:

struct Outer: Decodable {
    let spendingOutpoints: ArrayOrDict
    enum CodingKeys: String, CodingKey {
        case spendingOutpoints = "spending_outpoints"
    }
}

好的,我们开始:

let jsonData = json.data(using: .utf8)!
do {
    let result = try JSONDecoder().decode([Outer].self, from: jsonData)
} catch {
    print(error)
}

没有错误。因此,您应该能够重新构建您的实际数据结构。