如何将 TypeScript 的多值数组映射到 Go 结构体?

huangapple go评论70阅读模式
英文:

How to map a TypeScript array of multiple values to Go struct

问题

我正在尝试找到将 TypeScript 数组中的多个值映射到 Go 中的结构体的最佳方法。你有什么建议?

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

这是 JSON 格式的数据:

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

目前,我正在使用 interface{} 将其解组成一个结构体:

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

但是然后我必须对每个字段进行类型断言:

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

想知道是否有更好的方法。

英文:

I'm trying to find the best way to map a TypeScript array of multiple values to a struct in Go. What do you recommend?

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

This is the data in JSON format:

{
  "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{}

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.

答案1

得分: 2

你可以定义自己的结构体,并使用自定义的解析函数(unmarshal func)来实现,代码如下:

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
}

根据你的示例数据,我假设number是整数。如果JSON中可能包含浮点数(也是有效的number),你需要相应地调整类型...

英文:

You could define your own struct with a custom unmarshal func like this:

package main

import (
	&quot;encoding/json&quot;
	&quot;errors&quot;
	&quot;fmt&quot;
)

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

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

type Document struct {
	Data []Row `json:&quot;data&quot;`
}

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, &amp;elements); err != nil {
		return err
	}
	if len(elements) &lt; 6 {
		return errors.New(&quot;too few elements&quot;)
	}

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

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

	return nil
}

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

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

	return nil
}

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

Based in your example data I'm assuming the numbers are integers. If the JSON can contain floating point numbers (which would be valid number), you would have to adjust the type of course...

答案2

得分: 1

我认为你可以使用reflect来定义一个结构体,其中每个字段与data的类型索引相关,并类似于@some-user所做的方式定义UnmarshalJSON函数。

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

英文:

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.

package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
	&quot;reflect&quot;
	&quot;strconv&quot;
	&quot;strings&quot;
)

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

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

type Document struct {
	Data []*Row `json:&quot;data&quot;`
}

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

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:&quot;0&quot;`
	B string `idx:&quot;1&quot;`
}

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, &amp;items); err != nil {
		return err
	}

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

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

			if !optional || (optional &amp;&amp; len(items) &gt; idx) {
				if string(items[idx]) == &quot;null&quot; {
					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, &quot;,&quot;)
	if len(ops) &lt; 1 {
		err = fmt.Errorf(&quot;empty &#39;idx&#39; tag&quot;)
		return
	} else if len(ops) &gt; 1 {
		optional = ops[1] == &quot;optional&quot;
	}

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

	return
}

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

There are still some checks missing but it does illustrates the idea.

huangapple
  • 本文由 发表于 2022年9月8日 23:24:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/73651555.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定