当前位置:首页 > 文章列表 > Golang > Go问答 > "我能复制golang中具有不确定类型标签的接口指针吗?"

"我能复制golang中具有不确定类型标签的接口指针吗?"

来源:stackoverflow 2024-03-23 13:09:38 0浏览 收藏

在 Go 中,无法直接复制具有未知类型标签的接口指针的值。然而,有几种方法可以实现类似的功能: * **使用自定义编组器:**创建一个自定义编组器类型,它可以处理所有需要审查的类型。在编组器中实现一个方法来审查敏感字段。 * **使用泛型(Go 1.18 及更高版本):**创建泛型函数,该函数接受一个接口指针作为参数并实现指针接收器方法来审查敏感字段。 * **使用嵌入类型:**创建一个嵌入敏感字段类型的类型。在主类型中实现一个方法来审查敏感字段,该方法将调用嵌入类型的审查方法。

问题内容

我可以复制golang中类型未知的标签的接口指针的值吗

我有一个名为“censorpayload”的函数,它将接收一个指针并解组并审查一些具有名为“censored”标签的字段,但我不想更新名为“target”的参数中的值,这意味着我必须复制新接口{}的目标值。我尝试了很多方法,例如reflect.new,但是标签没有通过unmarshal或do函数。

type HelloWorld struct {
    Hello string `json:"hello"`
    Password string `json:"password" censored:"*****"`
}

a := HelloWorld{}

CensorPayload(&a, `{"hello": "world", "password": "12345"}`)

func CensorPayload(target interface{}, payload string) (string, error) {
    err := json.Unmarshal([]byte(payload), target)
    if err != nil {
        return "", err
    }
    err = Do(target)
    if err != nil {
        return "", err
    }
    censoredPayload, err := json.Marshal(target)
    if err != nil {
        return "", err
    }
    return string(censoredPayload), nil
}

正确答案


有很多方法可以改进和实现这一点。我将从一些改进开始,然后逐步实现您正在寻找的内容。

改进

首先,我会避免使用 target 接口{} 作为参数。绝对没有任何东西可以保证调用者将传入指针。至少,我会考虑使用 protobuf 用来确保接口 proto.message 的参数是指针值的技巧。这是通过向类型添加带有指针接收器的空方法来完成的。在这种情况下,这将是:

type jsonptr interface {
    jsonptr()
}

// implemented:
func (*helloworld) jsonptr() {} // empty

func censorpayload(target jsonptr, payload string) (string, error) {
    // pretty much the same as you have here
}

另一种选择是使用泛型,具体取决于您实际需要处理的类型数量,这可能是它们的有效用例:

type censortypes interface {
    *helloworld | *anothertype | *yetanother
}

func censorpayload[t censortypes](target t, payload string) (string, error) {
    // same as what you have now
}

通过嵌入减少重复

如果您有多种带有 password 字段的类型,所有这些类型都需要能够被审查,您可以将 password 字段拆分为自己的类型并将其嵌入到需要的位置:

type password struct {
    password string `json:"password"`
}
type helloworld struct {
    // regular fields here
    password // embed the password field
}

如果您需要经常使用此密码审查,我肯定会采用这种方法。最后,我将提供一个示例,其中我使用嵌入类型来完成您尝试以最少的代码重复完成的任务...

可能的实现

然而,这一切都是为了确保传递一个指针,无论如何接口方法已经做到了这一点。您希望能够审查密码,并在代码中进行特定的调用来执行此操作,那么为什么不在相关类型上实现方法,并将其添加到接口中。添加泛型,我们可以进行编译时检查,以确保相关方法正确实现(即使用指针接收器):

type censored interface {
    *helloworld | *anothertype // and so on
    censorpass()
}

func (h *helloworld) censorpass() { // pointer receiver
    h.password = "******"
}
// in case `password` is an embedded type, no need to implement it on `helloworld`
func (p *password) censorpass() {
    p.password = "******"
}

func censsorpayload[t censored](target t, payload string) (string, error) {
    // same as before until:
    // do() is replaced with:
    target.censorpass()
    // the rest is the same
}

现在,如果您的 helloworld 类型使用值接收器 (func (h helloworld) censorpass) 实现 censorpass,或者调用者无法传入指针值作为 target 参数,您将收到错误。

选项 2:自定义编组器

这里的另一个选择是创建一个包装类型,嵌入您需要处理的所有类型,并在所述类型上实现自定义编组器接口:

type wrapper struct {
    *helloworld
    *anothertype
    *yestanother
}

func (w wrapper) marshaljson() ([]byte, error) {
    if w.helloworld != nil {
        w.helloworld.password = "*****"
        return json.marshal(w.helloworld)
    }
    if w.anothertype != nil {
        w.anothertype.pass = "*****"
        w.anothertype.anothersensitivefield = "*******"
        return json.marshal(w.anothertype)
    }
    func w.yetanother != nil {
        w.yetanother.user = ""
        return json.marshal(w.yetanother)
    }
}

填充此 wrapper 类型都可以通过单个函数和类型开关来处理。我正在使用 any (下面是 interface{} 的缩写,但如果您想从其他包调用此方法,如果您单独从 censorpayload 调用它,我建议您出于上面给出的原因使用接口或泛型,并且您已经将其更改为使用接口/泛型,下面的代码就可以了:

func wrap(target any) (*wrapper, error) {
    switch tt := target.(type) {
    case *helloworld:
        return &wrapper{
            helloworld: tt,
        }, nil
    case *anothertype:
        return &wrapper{
            anothertype: tt,
        }, nil
    case *yetanother:
        return &wrapper{
            yetanother: tt,
        }, nil
    }
    // if we reach this point, the type passed to this function is unknown/can't be censored
    return nil, errors.new("type not supported by wrapper")
}

完成此操作后,您只需将 censorpayload 函数更改为如下所示:

// censorpayload using interface to ensure pointer argument
func censorpayload(target jsonptr, payload string) (string, error) {
     if err := json.unmarshal(target, []byte(payload)); err != nil {
         return "", err
     }
     wrapped, err := wrap(target)
     if err != nil {
         // type not handled by wrapper, either return an error
         return "", err // target was not supported
         // or in case that means this data should not be censored, just return the marhsalled raw data:
        raw, err := json.marshal(target)
        if err != nil {
            return "", err
        }
        return string(raw), nil
    }
    raw, err := json.marshal(wrapped) // this will call wrapper.marshaljson and censor what needs to be censored
    if err != nil {
        return "", err
    }
    return string(raw), nil
}

我想要什么

我可能会在 censorpayload 函数中使用 censored 类型。它的好处是根本不需要包装器,将了解要审查哪些字段/值的责任转移到数据类型,并确保 censorpass 方法正确实现(指针接收器),并且实际值是传递的是一个指针。为了将总 loc 降至最低,我也将其留给调用者将 []byte 返回值转换为字符串。 payload 参数也是如此。原始 json 数据表示为字节切片,因此 payload 参数(imo)应该反映这一点:

func censorpayload[t censored](target t, payload []byte) ([]byte, error) {
    if err := json.unmarshal(target, payload); err != nil {
        return nil, err
    }
    target.censorpass() // type knows what fields to censor
    return json.marshal(target) // return the data censored & marshalled
}

如上所述,我会将这种方法与嵌入类型的常见审查字段结合起来:

type password struct {
    password `json:"password"`
}
type anothersensitivefield struct {
    anothersensitivefield string `json:"example_db_ip"`
}
// password implements censorpass, is embedded so we don't need to do anything here
type helloworld struct {
    // fields
    password
}

// this one has 2 fields that implement censorpass
// we need to ensure both are called
type anothertype struct {
    // fields
    password
    anothersensitivefield
}

// this is a one-off, so we need to implement censorpass on the type
type yetanother struct {
   // all fields - no password
    user string `json:"user"` // this one needs to be censored
}

func (a *anothertype) censorpass() {
    a.password.censorpass()
    a.anothersensitivefield.censorpass()
}

func (y *yetanother) censorpass() {
    y.user = ""
}

func (p *password) censorpass() {
    p.password = "******"
}

func (a *anothersensitivefield) censorpass() {
    a.anothersensitivefield = "******"
}

任何嵌入一种类型(如 password)的类型都会自动具有 censorpass 方法。这些类型是嵌入的,并且没有其他嵌入类型有名称冲突,因此顶级类型可以将 helloworld.censorpass 解析为 helloworld.password.censorpass()。这不适用于 anothertype,因为它有 2 个提供此方法的嵌入类型。为了解决这个问题,我们只需在顶层实现该方法,并将调用传递给嵌入类型。第三个示例是我们想要审查尚未在其他任何地方使用的特定字段的示例。不需要创建单独的嵌入类型,因此我们可以直接在 yetanother 类型上实现该方法。如果我们有存在用户凭据的数据,并且我们希望审查不同位置的多个字段,我们可以轻松地为此创建一个类型:

type credentials struct {
    user string `json:"user"`
    pass string `json:"pass"`
}
type fullname struct {
    name  string `json:"name"`
    first string `json:"first_name"`
}

func (c *credentials) censorpass() {
    c.user = ""
    c.pass = "******"
}

func (f *fullname) censorpass() {
    if len(f.first) > 0 {
        f.first = string([]rune(f.first)[0])
    }
    if len(f.last) == 0 {
        return
    }
    last := make([]string, 0, 2)
    for _, v := range strings.split(f.last) {
        if len(v) > 0 {
            last = append(last, string([]rune(v)[0]))
        }
    }
    last = append(last, "") // for the final .
    f.last = strings.join(last, ". ") // initials only
}

我们所要做的就是将这种类型嵌入到我们需要的地方。完成后,我们的主要功能仍然如下所示:

func censorpayload[t censored](target t, payload []byte) ([]byte, error) {
    if err := json.unmarshal(target, payload); err != nil {
        return nil, err
    }
    target.censorpass() // type knows what fields to censor
    return json.marshal(target) // return the data censored & marshalled
}

但是现在,我们可以非常轻松地向此 censored 约束添加新类型:

type login struct {
    credentials // username pass
    fullname
}
type person struct {
    fullname
    status   string `json:"status"`
}
func (n *newtype) censorpass() {
    n.credentials.censorpass()
    n.fullname.censorpass()
}

通过这几行代码,我们现在还有 2 个类型可以传递到 censorpayload 函数(当然,通过更新约束)。给定 loginperson 有效负载,例如:

// login
{
    "user": "mylogin",
    "pass": "weakpass",
    "name": "pothering smythe",
    "first_name": "emanual",
}
// person
{
    "name": "doe",
    "first_name": "john",
    "status": "missing",
}

我们应该得到输出:

{
    "user": "",
    "pass": "******",
    "name": "P. S. "
    "first_name": "E",
}
{
    "name": "D. ",
    "first_name": "J",
    "status": "missing",
}

注意:

我已经编写了上面的所有代码,但没有先对其进行测试。可能会有拼写错误和错误,但它应该包含足够的有用信息和示例来帮助您入门。如果我有空闲时间,我可能会尝试一下并在需要时更新片段

文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《"我能复制golang中具有不确定类型标签的接口指针吗?"》文章吧,也可关注golang学习网公众号了解相关技术文章。

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
比较:r.PostFormValue(比较:r.PostFormValue("key")和r.PostForm.Get("key") 的最佳使用方法
上一篇
比较:r.PostFormValue("key")和r.PostForm.Get("key") 的最佳使用方法
Go是一种真正的编程语言吗?
下一篇
Go是一种真正的编程语言吗?
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • AI Make Song:零门槛AI音乐创作平台,助你轻松制作个性化音乐
    AI Make Song
    AI Make Song是一款革命性的AI音乐生成平台,提供文本和歌词转音乐的双模式输入,支持多语言及商业友好版权体系。无论你是音乐爱好者、内容创作者还是广告从业者,都能在这里实现“用文字创造音乐”的梦想。平台已生成超百万首原创音乐,覆盖全球20个国家,用户满意度高达95%。
    2次使用
  • SongGenerator.io:零门槛AI音乐生成器,快速创作高质量音乐
    SongGenerator
    探索SongGenerator.io,零门槛、全免费的AI音乐生成器。无需注册,通过简单文本输入即可生成多风格音乐,适用于内容创作者、音乐爱好者和教育工作者。日均生成量超10万次,全球50国家用户信赖。
    2次使用
  •  BeArt AI换脸:免费在线工具,轻松实现照片、视频、GIF换脸
    BeArt AI换脸
    探索BeArt AI换脸工具,免费在线使用,无需下载软件,即可对照片、视频和GIF进行高质量换脸。体验快速、流畅、无水印的换脸效果,适用于娱乐创作、影视制作、广告营销等多种场景。
    2次使用
  • SEO标题协启动:AI驱动的智能对话与内容生成平台 - 提升创作效率
    协启动
    SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
    9次使用
  • Brev AI:零注册门槛的全功能免费AI音乐创作平台
    Brev AI
    探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
    10次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码