मैं वर्तमान में एक एपीआई के साथ काम कर रहा हूं जो बस भविष्यवाणियों से संबंधित है। जेएसओएन के साथ एक दिलचस्प विचित्रता है जो एक निश्चित स्टॉप की भविष्यवाणियों के लिए लौटा दी जाती है। जब एक स्टॉप के लिए कई भविष्यवाणियां होती हैं, तो JSON कुछ इस तरह दिखता है:

...
"direction": {
            "prediction": [
                {
                    "affectedByLayover": "true",
                    "block": "241",
                    "dirTag": "loop",
                    "epochTime": "1571785998536",
                    "isDeparture": "false",
                    "minutes": "20",
                    "seconds": "1208",
                    "tripTag": "121",
                    "vehicle": "1698"
                },
                {
                    "affectedByLayover": "true",
                    "block": "241",
                    "dirTag": "loop",
                    "epochTime": "1571787798536",
                    "isDeparture": "false",
                    "minutes": "50",
                    "seconds": "3008",
                    "tripTag": "122",
                    "vehicle": "1698"
                },
                {
                    "affectedByLayover": "true",
                    "block": "241",
                    "dirTag": "loop",
                    "epochTime": "1571789598536",
                    "isDeparture": "false",
                    "minutes": "80",
                    "seconds": "4808",
                    "tripTag": "123",
                    "vehicle": "1698"
                }
            ],
            "title": "Loop"
        }
...

हालांकि, जब स्टॉप के लिए केवल एक भविष्यवाणी होती है, तो JSON इसके बजाय इस तरह दिखता है:

...
"direction": {
            "prediction": 
                {
                    "affectedByLayover": "true",
                    "block": "241",
                    "dirTag": "loop",
                    "epochTime": "1571785998536",
                    "isDeparture": "false",
                    "minutes": "20",
                    "seconds": "1208",
                    "tripTag": "121",
                    "vehicle": "1698"
                }
            "title": "Loop"
        }
...

ध्यान दें कि "भविष्यवाणी" अब एक सरणी के अंदर नहीं है - यह वह जगह है जहां मेरा मानना ​​​​है कि JSON को डिकोड करने के लिए स्विफ्ट कोडेबल प्रकार का उपयोग करते समय चीजें जटिल हो रही हैं। मेरा मॉडल "दिशा" और "भविष्यवाणी" के लिए इस तरह दिखता है

struct BTDirection: Codable {
    let title: String!
    let stopTitle: String!
    let prediction: [BTPrediction]!
}

struct BTPrediction: Codable {
    let minutes: String!
    let vehicle: String!
}

मूल रूप से जो हो रहा है वह है prediction में BTDirection BTPrediction की एक सरणी की तलाश में है, हालांकि ऊपर के दूसरे मामले में, यह एक ऐरे नहीं होगा इसलिए डिकोडिंग विफल हो जाती है। मैं अपने मॉडल को एक सरणी या एक वस्तु दोनों को समायोजित करने के लिए और अधिक लचीला कैसे बना सकता हूं? आदर्श रूप से, दूसरे मामले में prediction अभी भी एकल BTDirection की एक सरणी होगी। इस संबंध में किसी भी सहायता प्रशंसनीय होगी।

3
woakley5 23 अक्टूबर 2019, 02:01

3 जवाब

तुम कोशिश कर सकते हो

struct BTDirection:Codable {

    let title,stopTitle: String
    let prediction: [BTPrediction]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        title = try container.decode(String.self, forKey: .title)
        stopTitle = try container.decode(String.self, forKey: .stopTitle)
        do {
            let res = try container.decode([BTPrediction].self, forKey: .prediction)
            prediction = res
        }
        catch { 
              let res = try container.decode(BTPrediction.self, forKey: .prediction)
              prediction = [res] 
        }  
    }
}
1
Sh_Khan 23 अक्टूबर 2019, 02:07

Sh_Khan के उत्तर में जोड़ने के लिए, यदि आपके एपीआई प्रतिक्रियाओं में कई स्थान हैं जहां इस तरह की चीजें होती हैं, तो आप इस कस्टम डिकोडिंग और एन्कोडिंग को एक कस्टम रैपर प्रकार से निकाल सकते हैं ताकि आपको इसे हर जगह दोहराना न पड़े, जैसे:

/// Wrapper type that can be encoded/decoded to/from either
/// an array of `Element`s or a single `Element`.
struct ArrayOrSingleItem<Element> {
    private var elements: [Element]
}

extension ArrayOrSingleItem: Decodable where Element: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()

        do {
            // First try decoding the single value as an array of `Element`s.
            elements = try container.decode([Element].self)
        } catch {
            // If decoding as an array of `Element`s didn't work, try decoding
            // the single value as a single `Element`, and store it in an array.
            elements = try [container.decode(Element.self)]
        }
    }
}

extension ArrayOrSingleItem: Encodable where Element: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()

        if elements.count == 1, let element = elements.first {
            // If the wrapped array of `Element`s has exactly one `Element` 
            // in it, encode this as just that one `Element`.
            try container.encode(element)
        } else {
            // Otherwise, encode the wrapped array just as it is - an array
            // of `Element`s.
            try container.encode(elements)
        }
    }
}

// This lets you treat an `ArrayOrSingleItem` like a collection of elements.
// If you need the elements as type `Array<Element>`, just instantiate a new
// `Array` from your `ArrayOrSingleItem` like:
//     let directions: ArrayOrSingleItem<BTDirection> = ...
//     let array: [BTDirection] = Array(directions)
extension ArrayOrSingleItem: MutableCollection {
    subscript(position: Int) -> Element {
        get { elements[position] }
        set { elements[position] = newValue }
    }

    var startIndex: Int { elements.startIndex }
    var endIndex: Int { elements.endIndex }

    func index(after i: Int) -> Int {
        elements.index(after: i)
    }
}

// This lets you instantiate an `ArrayOrSingleItem` from an `Array` literal.
extension ArrayOrSingleItem: ExpressibleByArrayLiteral {
    init(arrayLiteral elements: Element...) {
        self.elements = elements
    }
}

तब आप बस अपने prediction (और किसी भी अन्य संपत्ति की घोषणा कर सकते हैं जिसमें आपके एपीआई प्रतिक्रिया में एक सरणी या एक आइटम होने की क्षमता है) इस तरह:

struct BTDirection: Codable {
    let title: String?
    let stopTitle: String?
    let prediction: ArrayOrSingleItem<BTPrediction>?
}
1
TylerTheCompiler 23 अक्टूबर 2019, 05:35

@TylerTheCompiler और @Sh_Khan दोनों समाधान में बहुत अच्छा तकनीकी इनपुट प्रदान करते हैं जो समाधान के यांत्रिकी प्रदान करते हैं, लेकिन प्रदान किया गया कोड दिए गए जेसन डेटा के साथ कुछ कार्यान्वयन मुद्दों को प्रभावित करेगा:

  1. JSON पोस्ट में त्रुटियां हैं जो इसके साथ काम करना बंद कर देगी - मुझे संदेह है कि ये सिर्फ कॉपी और पेस्ट त्रुटियां हैं, लेकिन यदि नहीं तो आपको आगे बढ़ने में समस्या होगी।
  2. प्रारंभिक direction कुंजी के कारण JSON में प्रभावी रूप से नेस्टिंग की 3 (या कम से कम 2.5!) परतें होती हैं। इसे या तो init(from:) में समतल करने की आवश्यकता होगी या, जैसा कि नीचे दिया गया है, मानचित्रण में आसानी के लिए एक अस्थायी संरचना की आवश्यकता है। प्रारंभकर्ता में समतल करना अधिक सुरुचिपूर्ण होगा, एक अस्थायी संरचना बहुत तेज है :-)
  3. CodingKeys, जबकि स्पष्ट है, पिछले उत्तरों में परिभाषित नहीं किया गया है, इसलिए init(from:) को संकलित करने में त्रुटियां उत्पन्न होंगी।
  4. JSON में कोई stopTitle फ़ील्ड नहीं है, जिससे डिकोडिंग में त्रुटि होगी जब तक कि इसे वैकल्पिक के रूप में नहीं माना जाता है। यहां मैंने इसे एक ठोस String के रूप में माना है और इसे डिकोडिंग में संभाला है; आप इसे केवल String? बना सकते हैं और फिर डिकोडर इसके अनुपस्थित होने का सामना करेगा।

"सही" JSON (जोड़े गए शुरुआती ब्रेसिज़, लापता कॉमा, आदि) का उपयोग करके निम्न कोड दोनों परिदृश्यों को आयात करेगा। मैंने arrayOrSingleItem को लागू नहीं किया है, क्योंकि इसका सारा श्रेय @TylerTheCompiler के पास है, लेकिन आप इसे आसानी से छोड़ सकते हैं।

struct Direction: Decodable {
   let direction: BTDirection
}

struct BTDirection: Decodable {
   enum CodingKeys: String, CodingKey {
      case title
      case stopTitle
      case prediction
   }
   let prediction: [BTPrediction]
   let title: String
   let stopTitle: String

   init(from decoder: Decoder) throws {
      let container = try decoder.container(keyedBy: CodingKeys.self)
      do {
         prediction = try container.decode([BTPrediction].self, forKey: .prediction)
      } catch {
         let singlePrediction = try container.decode(BTPrediction.self, forKey: .prediction)
         prediction = [singlePrediction]
      }
      title = try container.decode(String.self, forKey: .title)
      stopTitle = try container.decodeIfPresent(String.self, forKey: .stopTitle) ?? "unnamed stop"
   }
}

struct BTPrediction: Decodable {
   let minutes: String
   let vehicle: String
}

और फिर वास्तव में JSON को डीकोड करने के लिए शीर्ष-स्तरीय दिशा प्रकार को डिकोड करें

let data = json.data(using: .utf8)
if let data = data {
   do {
      let bus = try decoder.decode(Direction.self, from: data)
      // extract the BTDirection type from the temporary Direction type
      // and do something with the decoded data
   }catch {
      //handle error
   }
}

यदि आप जागरूक नहीं हैं, तो JSON Validator जोंस को मान्य/सुधार करने के लिए बहुत उपयोगी है।

0
flanker 23 अक्टूबर 2019, 12:54