英文:
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 number
s 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论