简体   繁体   English

如何将 map 一个 TypeScript 数组的多个值转换为 Go 结构

[英]How to map a TypeScript array of multiple values to Go struct

I'm trying to find the best way to map a TypeScript array of multiple values to a struct in Go.我试图找到 map 一个 TypeScript 数组到 Go 中的结构的最佳方法。 What do you recommend?你有什么建议吗?

data: [number, number, number, string, string, boolean, [string, number]?][];

This is the data in JSON format:这是 JSON 格式的数据:

{
  "data": [
    [
      35241,
      7753,
      7750,
      "0xbb2b8038a1640196fbe3e38816f3e67cba72d940",
      "spot",
      true
    ],
    [60259, 7746, null, "#7746/USD", "internal", true, ["requote", 145]]
  ]
}

For now, what I'm doing is unmarshaling it into a struct using interface{}现在,我正在做的是使用 interface{} 将其解组为结构

Data [][]interface{} `json:"data"`

But then I have to do a type assertion for every field但是我必须为每个字段做一个类型断言

for _, asset := range assetsAndPairs.Data.Pairs.Data {
    fmt.Println(asset[0].(float64))
}

Wondering if there is a better way.想知道有没有更好的方法。

You could define your own struct with a custom unmarshal func like this:您可以使用自定义解组函数定义自己的结构,如下所示:

package main

import (
    "encoding/json"
    "errors"
    "fmt"
)

var json_data = []byte(`{
  "data": [
    [
      35241,
      7753,
      7750,
      "0xbb2b8038a1640196fbe3e38816f3e67cba72d940",
      "spot",
      true
    ],
    [60259, 7746, null, "#7746/USD", "internal", true, ["requote", 145]]
  ]
}`)

func main() {
    var doc Document
    if err := json.Unmarshal(json_data, &doc); err != nil {
        panic(err)
    }
    for _, r := range doc.Data {
        fmt.Printf("%+v\n", r)
        if r.Optional != nil {
            fmt.Printf("  with optional: %+v\n", *r.Optional)
        }
    }
}

type Document struct {
    Data []Row `json:"data"`
}

type Row struct {
    SomeInt    int
    SomeString string
    // Simplified
    Optional *MoreData
}

type MoreData struct {
    SomeString string
    SomeInt    int
}

func (row *Row) UnmarshalJSON(data []byte) error {
    var elements []json.RawMessage
    if err := json.Unmarshal(data, &elements); err != nil {
        return err
    }
    if len(elements) < 6 {
        return errors.New("too few elements")
    }

    if err := json.Unmarshal(elements[0], &row.SomeInt); err != nil {
        return err
    }
    if err := json.Unmarshal(elements[3], &row.SomeString); err != nil {
        return err
    }

    if len(elements) == 7 {
        var more MoreData
        if err := json.Unmarshal(elements[6], &more); err != nil {
            return err
        }
        row.Optional = &more
    }

    return nil
}

func (more *MoreData) UnmarshalJSON(data []byte) error {
    var elements []json.RawMessage
    if err := json.Unmarshal(data, &elements); err != nil {
        return err
    }
    if len(elements) < 2 {
        return errors.New("too few elements")
    }

    if err := json.Unmarshal(elements[0], &more.SomeString); err != nil {
        return err
    }
    if err := json.Unmarshal(elements[1], &more.SomeInt); err != nil {
        return err
    }

    return nil
}

https://go.dev/play/p/zLgENBZ0fkC https://go.dev/play/p/zLgENBZ0fkC

Based in your example data I'm assuming the number s are integers.根据您的示例数据,我假设number s 是整数。 If the JSON can contain floating point numbers (which would be valid number ), you would have to adjust the type of course...如果 JSON 可以包含浮点数(这将是有效number ),那么您当然必须调整类型......

I think you could use reflect to define a struct with each field related to an index of data 's type and define the UnmarshalJSON function similarly to what @some-user did.我认为您可以使用reflect来定义一个结构,其中每个字段与data的类型的索引相关,并定义UnmarshalJSON function 类似于@some-user 所做的。

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
    "strconv"
    "strings"
)

var json_data = []byte(`{
    "data": [
      [
        35241,
        7753,
        7750,
        "0xbb2b8038a1640196fbe3e38816f3e67cba72d940",
        "spot",
        true
      ],
      [60259, 7746, null, "#7746/USD", "internal", true, ["requote", 145]]
    ]
  }`)

func main() {
    var doc Document
    if err := json.Unmarshal(json_data, &doc); err != nil {
        panic(err)
    }
    for _, r := range doc.Data {
        fmt.Printf("%+v\n", r)
        if r.G != nil {
            fmt.Printf("  with optional: %+v\n", r.G)
        }
    }
}

type Document struct {
    Data []*Row `json:"data"`
}

type Row struct {
    A int           `idx:"0"`
    B int           `idx:"1"`
    C int           `idx:"2"`
    D string        `idx:"3"`
    E string        `idx:"4"`
    F string        `idx:"5"`
    G *OptionalItem `idx:"6,optional"`
}

func (r *Row) UnmarshalJSON(data []byte) error {
    typ := reflect.TypeOf(r)
    val := reflect.ValueOf(r)

    return unmarshalArray(typ, val, data)
}

type OptionalItem struct {
    A string `idx:"0"`
    B string `idx:"1"`
}

func (oi *OptionalItem) UnmarshalJSON(data []byte) error {
    typ := reflect.TypeOf(oi)
    val := reflect.ValueOf(oi)

    return unmarshalArray(typ, val, data)
}

func unmarshalArray(typ reflect.Type, val reflect.Value, data []byte) error {
    var items []json.RawMessage
    if err := json.Unmarshal(data, &items); err != nil {
        return err
    }

    for i := 0; i < typ.Elem().NumField(); i++ {
        field := typ.Elem().Field(i)

        if value, ok := field.Tag.Lookup("idx"); ok {
            idx, optional, err := splitTag(value)
            if err != nil {
                return err
            }

            if !optional || (optional && len(items) > idx) {
                if string(items[idx]) == "null" {
                    continue
                }

                valueField := val.Elem().FieldByName(field.Name)
                if _, ok := valueField.Interface().(json.Unmarshaler); ok {
                    if err := json.Unmarshal(items[idx], valueField.Addr().Interface()); err != nil {
                        return err
                    }
                } else {
                    if valueField.Kind() == reflect.String {
                        valueField.SetString(string(items[idx]))
                    } else if valueField.Kind() == reflect.Int {
                        number, err := strconv.Atoi(string(items[idx]))
                        if err != nil {
                            return err
                        }
                        valueField.SetInt(int64(number))
                    } else {
                        valueField.Set(reflect.ValueOf(items[idx]))
                    }
                }
            }
        }
    }

    return nil
}

func splitTag(tag string) (idx int, optional bool, err error) {
    ops := strings.Split(tag, ",")
    if len(ops) < 1 {
        err = fmt.Errorf("empty 'idx' tag")
        return
    } else if len(ops) > 1 {
        optional = ops[1] == "optional"
    }

    idx, err = strconv.Atoi(ops[0])
    if err != nil {
        return
    }

    return
}

https://go.dev/play/p/x8hC_8JfjGG https://go.dev/play/p/x8hC_8JfjGG

There are still some checks missing but it does illustrates the idea.仍然缺少一些检查,但它确实说明了这个想法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM