使用Swift Codable解码以值作为键的JSON [英] Use swift Codable to decode JSON with values as keys

查看:100
本文介绍了使用Swift Codable解码以值作为键的JSON的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在解码JSON结构时遇到问题,我无法对其进行更改以使其更易于解码(它来自firebase).

I have a problem decoding a JSON structure which I cannot change to make it easier to decode (it's coming from firebase)..

如何将以下JSON解码为对象? 问题是如何转换"7E7-M001".这是带有抽屉的容器的名称.抽屉名称也用作键.

How do I decode the following JSON into objects? The problem is how to convert "7E7-M001". It's the name of a container which has drawers. The drawers name is also used as a key.

{
  "7E7-M001" : {
    "Drawer1" : {
      "101" : {
        "Partnumber" : "F101"
      },
      "102" : {
        "Partnumber" : "F121"
      }
    }
  },
  "7E7-M002": {
    "Drawer1": {
      "201": {
        "Partnumber": "F201"
      },
      "202": {
        "Partnumber": "F221"
      }
    }
  }
}

我必须在Container&中修复哪些内容?抽屉类是否具有作为标题属性的键以及这些类中的对象数组?

What do I have to fix in the Container & Drawer class to have the key as a title property and an array of objects in these classes ?

class Container: Codable {
    var title: String
    var drawers: [Drawer]
}

class Drawer: Codable {
    var title: String
    var tools: [Tool]
}

class Tool: Codable {
    var title: String
    var partNumber: String

    enum CodingKeys: String, CodingKey {
        case partNumber = "Partnumber"
    }
}

推荐答案

首先,我将做一些简单的简化,以便将重点放在这个问题的重点上.我将使所有内容不可变,用结构替换类,并仅实现Decodable.使其成为可编码状态是一个单独的问题.

First I'm going to make some slight simplifications so I can focus on the important points of this question. I'm going to make everything immutable, replace the classes with structs, and only implement Decodable. Making this Encodable is a separate issue.

用于处理未知值键的中央工具是CodingKey,它可以处理任何字符串:

The central tool for handling unknown value keys is a CodingKey that can handle any string:

struct TitleKey: CodingKey {
    let stringValue: String
    init?(stringValue: String) { self.stringValue = stringValue }
    var intValue: Int? { return nil }
    init?(intValue: Int) { return nil }
}

第二个重要工具是知道自己的标题的能力.这意味着向解码器询问我们在哪里?"那是当前编码路径中的最后一个元素.

The second important tool is the ability to know your own title. That means asking the decoder "where are we?" That's the last element in the current coding path.

extension Decoder {
    func currentTitle() throws -> String {
        guard let titleKey = codingPath.last as? TitleKey else {
            throw DecodingError.dataCorrupted(.init(codingPath: codingPath,
                                                    debugDescription: "Not in titled container"))
        }
        return titleKey.stringValue
    }
}

然后我们需要一种方法来对以标题"命名的元素进行解码:

And then we need a way to decode elements that are "titled" this way:

extension Decoder {
    func decodeTitledElements<Element: Decodable>(_ type: Element.Type) throws -> [Element] {
        let titles = try container(keyedBy: TitleKey.self)
        return try titles.allKeys.map { title in
            return try titles.decode(Element.self, forKey: title)
        }
    }
}

有了这一点,我们可以为这些有标题的"事物发明一种协议并将其解码:

With that, we can invent a protocol for these "titled" things and decode them:

protocol TitleDecodable: Decodable {
    associatedtype Element: Decodable
    init(title: String, elements: [Element])
}

extension TitleDecodable {
    init(from decoder: Decoder) throws {
        self.init(title: try decoder.currentTitle(),
                  elements: try decoder.decodeTitledElements(Element.self))
    }
}

这就是大部分工作.我们可以使用此协议使高层解码非常容易.只需实现init(title:elements:).

And that's most of the work. We can use this protocol to make decoding pretty easy for the upper-level layers. Just implement init(title:elements:).

struct Drawer: TitleDecodable {
    let title: String
    let tools: [Tool]
    init(title: String, elements: [Tool]) {
        self.title = title
        self.tools = elements
    }
}

struct Container: TitleDecodable {
    let title: String
    let drawers: [Drawer]

    init(title: String, elements: [Drawer]) {
        self.title = title
        self.drawers = elements
    }
}

Tool稍有不同,因为它是叶节点,并且还有其他要解码的内容.

Tool is a little different since it's a leaf node and has other things to decode.

struct Tool: Decodable {
    let title: String
    let partNumber: String

    enum CodingKeys: String, CodingKey {
        case partNumber = "Partnumber"
    }

    init(from decoder: Decoder) throws {
        self.title = try decoder.currentTitle()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.partNumber = try container.decode(String.self, forKey: .partNumber)
    }
}

这只是最顶层.我们将创建Containers类型只是为了包装内容.

That just leaves the very top level. We'll create a Containers type just to wrap things up.

struct Containers: Decodable {
    let containers: [Container]
    init(from decoder: Decoder) throws {
        self.containers = try decoder.decodeTitledElements(Container.self)
    }
}

并使用它,解码顶级Containers:

let containers = try JSONDecoder().decode(Containers.self, from: json)
print(containers.containers)

请注意,由于JSON对象不是按顺序保留的,因此数组可能与JSON的顺序不同,并且在两次运行之间的顺序也可能不同.

Note that since JSON objects are not order-preserving, the arrays may not be in the same order as the JSON, and may not be in the same order between runs.

要点

这篇关于使用Swift Codable解码以值作为键的JSON的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆