Penafian: Ada beberapa pertanyaan serupa yang diposting, tetapi tidak satu pun dari mereka yang tampaknya menduplikasi pertanyaan saya

Misalkan aplikasi saya menerima JSON dari API yang direkayasa dengan buruk, dan JSON yang terlihat seperti ini:

{
    "matches": {
        "page1": [{
            "name": "John",
            "surname": "Doe",
            "interests": [{
                    "id": 13,
                    "text": "basketball"
                },
                {
                    "id": 37,
                    "text": "competitive knitting"
                },
                {
                    "id": 127,
                    "text": "romcoms"
                }
            ]
        }],
        "page2": [{
            "name": "Dwayne",
            "surname": "Johnson",
            "interests": [{
                    "id": 42,
                    "text": "sci-fi"
                },
                {
                    "id": 255,
                    "text": "round numbers"
                }
            ]
        }]
    }
}

Jika saya ingin mendapatkan, katakanlah, semua minat dari semua kecocokan, dalam fungsi asli Swift, pertama-tama saya harus melakukan beberapa hal seperti ini:

struct MatchesData: Codable {
    let matches: Matches
}

struct Matches: Codable {
    let page1: Page1
    let page2: Page2
}

struct Page1: Codable {
    let interests: [Interest]
}

struct Page2: Codable {
    let interests: [Interest]
}

struct Interest: Codable {
    let id: Int
    let text: String
}

Kemudian, saya harus menggunakan struct yang saya buat, dengan cara seperti ini:

func handleJSON(_ response: Data) {
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(MatchesData.self, from: response)
            // Only here I can start actually working with the data API sent me, it's in decodedData
            ...
        } catch {
            // Handle the errors somehow
            return
        }
}

Meskipun agak ini berfungsi, ia memiliki dua kelemahan utama.

Pertama, semua yang disatukan adalah jumlah persiapan dan kode yang gila secara umum untuk tugas sesederhana itu, dan itu tidak sesuai dengan prinsip KERING.

Terakhir, pendekatan ini tidak akan berfungsi jika Anda tidak mengetahui struktur JSON secara pasti sebelumnya. Misalnya, dalam contoh saya, bagaimana jika jumlah halaman tidak ditetapkan menjadi 2, tetapi bisa berada di antara, katakanlah, 1 dan 50?

Dalam bahasa lain yang memiliki alat bawaan yang waras untuk bekerja dengan JSON, saya baru saja menguraikan JSON dan secara rekursif mengulangi kecocokan untuk melakukan apa yang saya butuhkan dengan konten.

Contoh (tanpa tindakan pencegahan keamanan untuk meningkatkan keterbacaan):

J:

const handleJSON = jsonStr => {
  const jsonObj = JSON.parse(jsonStr)
  const matches = jsonObj.matches
  Object.values(matches).forEach(page => {
    // Recursively process every page to find what I need and process it
  })
}

Python 3:

import json

def handleJSON(jsonStr):
  jsonObj = json.loads(jsonStr)
  matches = jsonObj['matches']
  for page in matches:
    # Recursively process every page to find what I need and process it

PHP:

function handleJSON($jsonStr) {
    $jsonObj = json_decode($jsonStr);
    $matches = $jsonObj->matches;
    foreach ($matches as $page) {
        // Recursively process every page to find what I need and process it
    }
}

Jadi, pertanyaannya adalah, bagaimana saya mencapai hal yang sama di Swift dengan cara yang waras, seperti pada contoh di atas (yang pasti berarti jumlah kode yang kira-kira sama)? Jika Anda mengetahui lib pihak ke-3 yang melakukan hal itu, saya akan dengan senang hati menerima lib seperti itu sebagai jawaban.

PEMBARUAN: Baru saja mengetahui tentang Jsonify, yang tampaknya setidaknya sebagai solusi yang bagus untuk masalah saya sebagai SwiftyJSON, bahkan mungkin lebih baik. Seseorang mungkin harus mencoba keduanya sebentar untuk memutuskan mana yang lebih sesuai dengan selera dan kebutuhan mereka

0
Andy Mac 18 Januari 2020, 18:41

2 jawaban

Jawaban Terbaik

Jika Anda mencari perpustakaan eksternal yang melakukan ini, Anda dapat mencoba SwiftyJSON, yang memungkinkan Anda untuk mendapatkan semua kepentingan seperti ini:

    let json = JSON(parseJSON: jsonString)
    let matchesObject = json["matches"]
    // I assume the total number of pages is stored in numberOfPages
    let interests = (0..<2).flatMap {
        matchesObject["page\($0 + 1)"].arrayValue.flatMap {
            $0["interests"].arrayValue.map {
                // using the same Interest struct as in your question
                Interest(id: $0["id"].intValue, text: $0["text"].stringValue)
            }
        }
    }
1
Sweeper 18 Januari 2020, 16:05

Pertama-tama, perlu diingat bahwa struct tidak perlu memiliki cakupan global. Anda dapat mendefinisikan struct "sementara" dalam suatu fungsi hanya untuk tujuan membantu Anda menyelami JSON dan mengambil nilai. Jadi keseluruhan arsitektur kode sama sekali tidak dirusak.

Kedua, contoh JSON Anda sendiri tidak segila yang Anda sarankan; apa yang gila adalah struct Anda sendiri. Pertama, Anda menggunakan dua struct identik. Tidak ada tujuan apa pun untuk memiliki struct Page1 dan struct Page2. Kedua, tidak ada tujuan untuk Pertandingan Anda juga. Bukan itu JSON Anda. Di MatchesData Anda, properti matches seharusnya hanya berupa kamus bertipe [String:[Person]] (Anda tidak memiliki tipe Orang tetapi memang seperti itulah kelihatannya).

Jika klaim Anda adalah bahwa kamus seharusnya merupakan array di JSON, baiklah, saya setuju. Jika kunci selalu diberi nama "page1", "page2", dll., JSON konyol dalam hal itu. Tetapi kemudian Anda dapat nanti mengubah [String:[Person]] menjadi larik Person yang diurutkan berdasarkan angka di akhir kunci String "page". Dan kemudian Anda dapat memetakannya ke dalam array Ketertarikan jika Anda ingin membuang sisa info Orang.

Dengan kata lain: Bagaimana Anda mengurai data yang sampai kepada Anda dan bagaimana Anda memelihara data yang Anda minati, adalah dua hal yang sangat berbeda. Parsing data baru saja keluar dari dunia JSON dan masuk ke dunia objek; kemudian mengubahnya menjadi bentuk yang berguna bagi Anda, menjaga apa yang Anda butuhkan dan mengabaikan apa yang tidak Anda butuhkan.

Berikut ini contoh hal yang mungkin Anda lakukan:

let data = s.data(using: .utf8)!

struct Result : Codable {
    let matches:[String:[Person]]
}
struct Person : Codable {
    let name:String
    let surname:String
    let interests:[Interests]
}
struct Interests : Codable {
    let id:Int
    let text:String
}
let result = try! JSONDecoder().decode(Result.self, from: data)

Oke, jadi sekarang kita telah menguraikan data terkutuk itu. Tapi kami membenci strukturnya. Jadi ubahlah!

// okay, but dictionary is silly: flatten to an array
let persons1 = Array(result.matches)
func pageNameToNumber(_ s:String) -> Int {
    let num = s.dropFirst(4)
    return Int(num)!
}
let persons2 = persons1.map {(key:pageNameToNumber($0.key), value:$0.value)}
let persons3 = persons2.sorted {$0.key < $1.key}
let persons4 = persons3.map { $0.value }

Tapi kami masih memiliki array satu elemen konyol untuk ditangani:

// okay, but the one-element array is silly, so flatten that too
let persons5 = persons4.map {$0.first!}

Dan sekarang Anda mengatakan bahwa Anda tidak pernah peduli tentang hal-hal orang sama sekali, yang Anda inginkan hanyalah daftar minat:

// okay but all I care about are the interests
let interests = persons5.map {$0.interests}

(Dan kemudian Anda bisa meratakan minat juga, atau apa pun.)

Sekarang, jika semua itu dilakukan dalam suatu metode, maka hanya struktur Interests yang perlu menjadi publik; yang lainnya hanyalah alat untuk mendapatkan nilai yang diekstraksi, dan dapat bersifat pribadi ke bagian dalam metode. Hanya mempublikasikan struct yang benar-benar dibutuhkan/diinginkan aplikasi Anda untuk pemeliharaan data. Namun, pelajaran keseluruhannya adalah: cukup parsing JSON terkutuk menjadi objek: sekarang Anda berada di dunia objek Swift, dan dapat mengurai ulang objek-objek itu dengan cara apa pun yang pada akhirnya berguna bagi Anda.

5
matt 18 Januari 2020, 16:34