Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Takrif jenis generik untuk unmarshalling struct ke dalam kepingan

Takrif jenis generik untuk unmarshalling struct ke dalam kepingan

王林
王林ke hadapan
2024-02-06 08:54:041085semak imbas

Takrif jenis generik untuk unmarshalling struct ke dalam kepingan

Kandungan soalan

Saya mempunyai API yang biasanya mengembalikan tatasusunan sebagai objek yang mengandungi tatasusunan. Ambil contoh berikut sebagai contoh:

<code>{
  "items": {
    "number": 3,
    "item": [
      { ... } // Not relevant
    ]
  }
}
</code>

API melakukan ini di berpuluh-puluh tempat, setiap kali dengan nama yang berbeza. Ini dijamin berlaku dengan hanya dua kekunci: satu daripadanya ialah number dan satu lagi ialah tatasusunan.

Ini menjadikan struktur yang terhasil agak tidak menyenangkan untuk digunakan, kerana anda perlu sentiasa menavigasi melalui tahap medan yang tidak perlu.

Saya pada asasnya mahu antara muka Go saya berpura-pura ia mempunyai format ini:

<code>{
  "items": [
    { ... } // Not relevant
  ]
}
</code>

Satu pilihan ialah menulis fungsi UnmarshalJSON tersuai untuk setiap kejadian, tetapi ini kelihatan menyusahkan, terutamanya memandangkan ia muncul dalam hampir setiap struktur. Penyelesaian yang saya buat adalah jenis generik yang boleh mengendalikannya sendiri.

Percubaan saya sekarang adalah seperti berikut:

<code>// NestedArray tries to pull an unnecessarily nested array upwards
type NestedArray[T any] []T

func (n *NestedArray[T]) UnmarshalJSON(bytes []byte) error {
    // First unmarshal into a map
    target := make(map[string]interface{})

    err := json.Unmarshal(bytes, &target)
    if err != nil {
        return err
    }

    // Then find the nested array (key is unknown, so go off of the type instead)
    var sliceVal interface{}
    for k, v := range target {
        if k == "number" {
            continue
        }

        rt := reflect.TypeOf(v)
        if rt.Kind() == reflect.Slice {
            sliceVal = v
            break
        }
    }

    // Missing or empty, doesn't matter - set the result to nil
    if sliceVal == nil {
        *n = nil
        return nil
    }

    // Turn back into JSON and parse into correct target
    sliceJSON, err := json.Marshal(sliceVal)
    if err != nil {
        return err
    }

    err = json.Unmarshal(sliceJSON, n)  // Error occurs here
    if err != nil {
        return err
    }

    return nil
}
</code>

Cara penggunaan adalah seperti berikut:

<code>type Item struct {
  // Not relevant
}

type Root struct {
    // Use generic type to parse a JSON object into its nested array
    Items NestedArray[Item] `json:"items,omitempty"`
}
</code>

menghasilkan ralat berikut:

json: cannot unmarshal array into Go struct field Root.items of type map[string]interface{}

UnmarshalJSON 代码的最大部分似乎是正确的,因为我的调试器向我显示 sliceVal 正是我所期望的。解组回 NestedArray[T] Bahagian terbesar kod nampaknya betul, kerana penyahpepijat saya menunjukkan kepada saya bahawa sliceVal adalah seperti yang saya jangkakan. Ralat menyahmarsha kembali ke jenis NestedArray[T].

Adakah cara untuk menyelesaikan masalah ini? Adakah terdapat cara yang lebih baik daripada apa yang saya lakukan sekarang? Ini nampaknya paling bersih kepada saya, tetapi saya terbuka kepada cadangan.


Jawapan betul


Kaedah NestedArray[T].UnmarshalJSON memanggil dirinya secara rekursif. Panggilan dalaman menimbulkan ralat kerana ia memerlukan bytes 中的 JSON 对象,但它收到了一个 JSON 数组。通过解组到 []T 而不是 NestedArray[T] untuk dibetulkan.

Tidak berkaitan dengan ralat, kaedah NestedArray[T].UnmarshalJSON melakukan beberapa pengekodan dan penyahkodan yang tidak perlu. Gunakan json.RawMessage untuk membetulkannya.

Berikut ialah kod dengan kedua-dua pembetulan:

func (n *NestedArray[T]) UnmarshalJSON(bytes []byte) error {
    // First unmarshal into a map
    var target map[string]json.RawMessage
    err := json.Unmarshal(bytes, &target)
    if err != nil {
        return err
    }

    // Then find the nested array (key is unknown, so go off of the type instead)
    var array json.RawMessage
    for k, v := range target {
        if k == "number" {
            continue
        }
        if len(v) > 0 && v[0] == '[' {
            array = v
            break
        }
    }

    // Missing or empty, doesn't matter - set the result to nil
    if array == nil {
        *n = nil
        return nil
    }

    // Avoid recursive call to this method by unmarshalling to a []T.
    var v []T
    err = json.Unmarshal(array, &v)
    *n = v
    return err
}

Jalankan kod di Taman Permainan! .

Atas ialah kandungan terperinci Takrif jenis generik untuk unmarshalling struct ke dalam kepingan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:stackoverflow.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam