简体   繁体   English

可以在 golang 的结构中获取单个字段的 JSON 标签吗? 不是整个结构的字段

[英]Possible to get JSON tag of a single field within a struct in golang? Not the entire struct's fields

Similar to this question on StackOverflow, except I want to be able to get the JSON tag of a single field within a struct, not all the tags for a struct.与 StackOverflow 上的这个问题类似,除了我希望能够获取结构中单个字段的 JSON 标签,而不是结构的所有标签。

Get JSON field names of a struct 获取结构的 JSON 个字段名称

What I'm trying to do: I'm writing an edit API for a server, but only the values that are being edited will be sent in. I have individual update function for my Postgres server so I'd like to be able to do something like this.我正在尝试做的事情:我正在为服务器编写编辑 API,但只会发送正在编辑的值。我的 Postgres 服务器有单独的更新 function,所以我希望能够做这样的事情。

pseudocode:伪代码:

type Example struct {
    title String (json field)
    publisher String (json field)
 }

 var json ...

if fieldExists(title) {
     updateTitle(json[getField(example.title))
}

Use a struct value and the name of the field to get the tag:使用结构值和字段名称来获取标签:

// jsonTag returns the json field tag with given field name 
// in struct value v.  The function returns "", false if the
// struct does not have a field with the given name or the 
// the named field does not have a JSON tag.
func jsonTag(v interface{}, fieldName string) (string, bool) {
    t := reflect.TypeOf(v)
    sf, ok := t.FieldByName(fieldName)
    if !ok {
        return "", false
    }
    return sf.Tag.Lookup("json")
}

Example:例子:

fmt.Println(jsonTag(Example{}, "Foo"))

https://go.dev/play/p/-47m7ZQ_-24 https://go.dev/play/p/-47m7ZQ_-24

You cannot get the tag from the field because the tag is part of the struct's type, not part of the field's type.您无法从字段中获取标签,因为标签是结构类型的一部分,而不是字段类型的一部分。

It is possible to get the JSON tag of a single field within a struct using only the struct and a pointer to the field.可以仅使用结构和指向该字段的指针来获取结构中单个字段的 JSON 标记。 To be clear, this solution doesn't require any prior knowledge of the name of the field in string form.需要明确的是,此解决方案不需要事先了解字符串形式的字段名称。 This is important because this solution is more likely to survive future refactoring that might change the name of the field.这很重要,因为此解决方案更有可能在未来可能更改字段名称的重构中幸存下来。

Most of my solution is code borrowed from the request validation library at https://github.com/jellydator/validation我的大部分解决方案是从https 的请求验证库中借用的代码://github.com/jellydator/validation

The code works like this代码是这样工作的

type UpdateRequest struct {
    Description     string `json:"description,omitempty"`
    PrivatePods     bool   `json:"private_pods"`
    OperatingMode   string `json:"operating_mode,omitempty"`
    ActivationState string `json:"activation_state,omitempty"`
}

func main() {
    var request UpdateRequest

    jsonTag, err2 := FindStructFieldJSONName(&request, &request.OperatingMode)
    if err2 != nil {
        fmt.Println("field not found in struct")
        return
    }

    fmt.Println("JSON field name: " + jsonTag)
}

The output from the above example is上例中的 output 是

JSON field name: operating_mode

As you can see the only two inputs are a pointer to a struct and a pointer to a field within that struct.如您所见,仅有的两个输入是指向结构的指针和指向该结构内字段的指针。 The code works like this...代码是这样工作的……

FindStructFieldJSONName - This function does a little reflection on the struct pointer to get some values and assert that the pointer really points to a struct. FindStructFieldJSONName - 这个 function 对结构指针做了一些反射以获取一些值并断言该指针确实指向一个结构。 It also does some reflection on the field pointer to assert it really does point to a field.它还会对字段指针进行一些反射,以断言它确实指向一个字段。 This function then calls findStructField to find the field within the struct, essentially making sure the field pointer points to a field within the struct.这个 function 然后调用findStructField来查找结构中的字段,本质上确保字段指针指向结构中的字段。 Finally, this function calls getErrorFieldName to get the value of the json tag.最后这个function调用getErrorFieldName获取json标签的值。

findStructField - uses reflection to get a *reflect.StructField for the given field within the given struct findStructField - 使用反射为给定结构中的给定字段获取*reflect.StructField

getErrorFieldName - uses reflection to read the json tag from the field. getErrorFieldName - 使用反射从字段中读取 json 标记。

NOTE: In the context of OP's question and the context of this answer the function name, getErrorFieldName , doesn't make much sense.注意:在 OP 问题的上下文和此答案的上下文中, function 名称getErrorFieldName没有多大意义。 It might be better named as getJSONFieldName .将其命名为getJSONFieldName可能更好。 I copied this function from library that uses this code in a different context.我从在不同上下文中使用此代码的库中复制了此 function。 I chose not to rename any of it so that you can more easily refer to the original source.我选择不重命名其中的任何一个,以便您可以更轻松地参考原始来源。

You can try it out at the Go Playground.您可以在 Go 游乐场试用。

https://go.dev/play/p/7BWjPWX1G7d https://go.dev/play/p/7BWjPWX1G7d

Complete code posted here for reference:完整代码贴在这里供参考:

// You can edit this code!
// Click here and start typing.
package main

import (
    "fmt"
    "reflect"
    "strings"

    "github.com/friendsofgo/errors"
)

const (
    // ErrorTag is the struct tag name used to customize the error field name for a struct field.
    ErrorTag = "json"
)

var (
    ErrInternal = errors.New("internal validation error")
)

// FindStructFieldJSONName gets the value of the `json` tag for the given field in the given struct
// Implementation inspired by https://github.com/jellydator/validation/blob/v1.0.0/struct.go#L70
func FindStructFieldJSONName(structPtr interface{}, fieldPtr interface{}) (string, error) {
    value := reflect.ValueOf(structPtr)
    if value.Kind() != reflect.Ptr || !value.IsNil() && value.Elem().Kind() != reflect.Struct {
        // must be a pointer to a struct
        return "", errors.Wrap(ErrInternal, "must be a pointer to a struct")
    }
    if value.IsNil() {
        // treat a nil struct pointer as valid
        return "", nil
    }
    value = value.Elem()

    fv := reflect.ValueOf(fieldPtr)
    if fv.Kind() != reflect.Ptr {
        return "", errors.Wrap(ErrInternal, "must be a pointer to a field")
    }
    ft := findStructField(value, fv)
    if ft == nil {
        return "", errors.Wrap(ErrInternal, "field not found")
    }
    return getErrorFieldName(ft), nil
}

// findStructField looks for a field in the given struct.
// The field being looked for should be a pointer to the actual struct field.
// If found, the field info will be returned. Otherwise, nil will be returned.
// Implementation borrowed from https://github.com/jellydator/validation/blob/v1.0.0/struct.go#L134
func findStructField(structValue reflect.Value, fieldValue reflect.Value) *reflect.StructField {
    ptr := fieldValue.Pointer()
    for i := structValue.NumField() - 1; i >= 0; i-- {
        sf := structValue.Type().Field(i)
        if ptr == structValue.Field(i).UnsafeAddr() {
            // do additional type comparison because it's possible that the address of
            // an embedded struct is the same as the first field of the embedded struct
            if sf.Type == fieldValue.Elem().Type() {
                return &sf
            }
        }
        if sf.Anonymous {
            // delve into anonymous struct to look for the field
            fi := structValue.Field(i)
            if sf.Type.Kind() == reflect.Ptr {
                fi = fi.Elem()
            }
            if fi.Kind() == reflect.Struct {
                if f := findStructField(fi, fieldValue); f != nil {
                    return f
                }
            }
        }
    }
    return nil
}

// getErrorFieldName returns the name that should be used to represent the validation error of a struct field.
// Implementation borrowed from https://github.com/jellydator/validation/blob/v1.0.0/struct.go#L162
func getErrorFieldName(f *reflect.StructField) string {
    if tag := f.Tag.Get(ErrorTag); tag != "" && tag != "-" {
        if cps := strings.SplitN(tag, ",", 2); cps[0] != "" {
            return cps[0]
        }
    }
    return f.Name
}

type UpdateRequest struct {
    Description     string `json:"description,omitempty"`
    PrivatePods     bool   `json:"private_pods"`
    OperatingMode   string `json:"operating_mode,omitempty"`
    ActivationState string `json:"activation_state,omitempty"`
}

func main() {
    var request UpdateRequest

    jsonTag, err2 := FindStructFieldJSONName(&request, &request.OperatingMode)
    if err2 != nil {
        fmt.Println("field not found in struct")
        return
    }

    fmt.Println("JSON field name: " + jsonTag)
}

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

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