英文:
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 (
"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
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 (
"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
There are still some checks missing but it does illustrates the idea.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论