当前位置:首页 > 文章列表 > Golang > Go问答 > 将大括号{}转换为键值对{}

将大括号{}转换为键值对{}

来源:stackoverflow 2024-02-12 21:00:28 0浏览 收藏

怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《将大括号{}转换为键值对{}》,涉及到,有需要的可以收藏一下

问题内容

我的目标是从 json 生成 map[string]interface 结构,其中所有嵌套的 []interface{} 都被转换为 []map[string]interface{} 。这是因为我们使用模块 https://github.com/ivahaev/go-xlsx-templater 从 json 填充 xlsx 。所有数据预计都会进入 map[string]interface{} 结构体,其中所有嵌套的 []interface{} 都是 []map[string]interface

将 json 作为输入,如下所示:

{
    "totalamount": 4,
    "subtotal": 4,
    "vendors": [{
            "methodoftenders": [{
                "order": 1,
                "fees": 2
            }, {
                "order": 1,
                "fees": 2
            }],
            "subtotalfees": 4
        },
        {
            "methodoftenders": [{
                "order": 1,
                "fees": 2
            }, {
                "order": 1,
                "fees": 1
            }],
            "subtotalfees": 3
        }
    ]
}

当解组到 map[string]interface{} 时。我得到以下结构:

map[string]interface{}:
  "totalamount" : 4 interface{}(float64)
  "subtotal" : 4 interface{}(float64)
  "vendors" : interface{}([]interface{})
       [0]: interface(map[string]interface{})
           "methodoftenders" : interface{}([]interface{})
                 [0] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64)
                    "fees"  : 2 interface{}(float64)
                 [1] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64),
                    "fees"  : 2 interface{}(float64)
           "subtotalfees" : 4 interface{}(float64)
       [1]: interface(map[string]interface{}) 
            "methodoftenders" : interface{}([]interface{})
                  [0] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64)
                    "fees"  : 2 interface{}(float64)
                 [1] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64),
                    "fees"  : 2 interface{}(float64)
           "subtotalfees" : 3 interface{}(float64)

进行一些解析后,对每个 []interface{} 进行范围分析并创建一个 []map[string]interface{} 来存储每个嵌套的 map[string]interface 。

我得到了所需的结果,其中所有 []interfaces{} 都是 []map[string]interface{}

map[string]interface{}:
  "totalAmount" : 4 interface{}(float64)
  "subtotal" : 4 interface{}(float64)
  "Vendors" : interface{}([]map[string]interface{})
       [0]: interface(map[string]interface{})
           "MethodOfTenders" : interface{}([]map[string]interface{})
                 [0] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64)
                    "fees"  : 2 interface{}(float64)
                 [1] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64),
                    "fees"  : 2 interface{}(float64)
           "subtotalFees" : 4 interface{}(float64)
       [1]: interface(map[string]interface{}) 
            "MethodOfTenders" : interface{}([]map[string]interface{})
                  [0] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64)
                    "fees"  : 2 interface{}(float64)
                 [1] : interface(map[string]interface{})
                    "order" : 1 interface{}(float64),
                    "fees"  : 2 interface{}(float64)
           "subtotalFees" : 3 interface{}(float64)

有没有办法递归遍历所有map[string]接口并更改[]map[string]接口的[]interfaces{}?

编辑: 这里是包含完整模板和 json 的存储库,当前使用反射方法。

模板和 json 存储库


正确答案


这个技巧是使用反射来执行的。递归地向下检查值的类型:

// "casts" map values to the desired type recursively
func castmap(m map[string]any) map[string]any {
    for k := range m {
        switch reflect.valueof(m[k]).kind() {
        case reflect.map:
            mm, ok := m[k].(map[string]any)
            if !ok {
                panic(fmt.errorf("expected map[string]any, got %t", m[k]))
            }
            m[k] = castmap(mm)
        case reflect.slice, reflect.array:
            ma, ok := m[k].([]any)
            if !ok {
                panic(fmt.errorf("expected []any, got %t", m[k]))
            }
            m[k] = castarray(ma)
        default:
            // fmt.printf("%s: %t, kind %v\n", k, m[k], reflect.valueof(m[k]).kind())
            continue
        }
    }
    return m
}

// "casts" slice elements to the desired types recursively
func castarray(a []any) []map[string]any {
    res := []map[string]any{}
    for i := range a {
        switch reflect.valueof(a[i]).kind() {
        case reflect.map:
            am, ok := a[i].(map[string]any)
            if !ok {
                panic(fmt.errorf("expected map[string]any, got %t", a[i]))
            }
            am = castmap(am)
            res = append(res, am)
        default:
            panic(fmt.errorf("expected map[string]any, got %t", a[i]))
        }
    }
    return res
}

main 的完整示例位于此处:https://go.dev/play/p/MEQRe-f3dY1

输出:

before: map[string]interface {}{"vendors":[]interface {}{map[string]interface {}{"methodoftenders":[]interface {}{map[string]interface {}{"fees":2, "order":1}, map[string]interface {}{"fees":2, "order":1}}, "subtotalfees":4}, map[string]interface {}{"methodoftenders":[]interface {}{map[string]interface {}{"fees":2, "order":1}, map[string]interface {}{"fees":1, "order":1}}, "subtotalfees":3}}, "subtotal":4, "totalamount":4}
after: map[string]interface {}{"vendors":[]map[string]interface {}{map[string]interface {}{"methodoftenders":[]map[string]interface {}{map[string]interface {}{"fees":2, "order":1}, map[string]interface {}{"fees":2, "order":1}}, "subtotalfees":4}, map[string]interface {}{"methodoftenders":[]map[string]interface {}{map[string]interface {}{"fees":2, "order":1}, map[string]interface {}{"fees":1, "order":1}}, "subtotalfees":3}}, "subtotal":4, "totalamount":4}

看到了吗? "vendors"[]interface {},变成了 []map[string]interface {} "vendors[].methodoftenders"[]interface {},变成了 []map[string]interface {}

如果函数发现意外情况,就会出现恐慌。如果需要,请随意修改它们以返回 error

更新

这是使用类型断言的相同递归算法。

// "casts" map values to the desired type recursively
func castmap(m map[string]any) map[string]any {
    for k := range m {
        mm, ok := m[k].(map[string]any)
        if ok {
            m[k] = castmap(mm)
            continue
        }
        ma, ok := m[k].([]any)
        if ok {
            m[k] = castarray(ma)
            continue
        }
    }
    return m
}

// "casts" slice elements to the desired types recursively
func castarray(a []any) []map[string]any {
    res := []map[string]any{}
    for i := range a {
        am, ok := a[i].(map[string]any)
        if ok {
            am = castmap(am)
            res = append(res, am)
        } else {
            panic(fmt.errorf("expected map[string]any, got %t", a[i]))
        }
    }
    return res
}

完整代码https://go.dev/play/p/IbOhQqpisie

基准

其中一位评论者声称 reflection 很昂贵 。这里的情况并非如此:

goos: windows
goarch: amd64
pkg: example.org/try/test
cpu: intel(r) core(tm) i7-8550u cpu @ 1.80ghz
benchmarkcastreflect-8        382574          2926 ns/op        2664 b/op         29 allocs/op
benchmarkcasttype-8           418257          2934 ns/op        2664 b/op         29 allocs/op

基准在这里:https://go.dev/play/p/Ro8PeVQy8kA

演示不执行基准测试。它应该在真实的cpu上运行

更新

所以我注意到您添加了一个指向包含您目前拥有的内容的存储库的链接。该模板很奇怪(其中的某些值不是示例数据的一部分)。我已经删除了除您拥有的示例数据 json 中实际存在的字段之外的所有字段。事实证明,您使用的包的边缘非常粗糙(它确实处理嵌套值中的类型断言,但不是在顶层)。我个人要么分叉该包并解决该问题,但与此同时,如果您知道要迭代哪些字段,我可以在大约 5 分钟内完成所有工作:

var jsonmap map[string]interface{}
data := sampledata()
err := json.unmarshal(data, &jsonmap)
if err != nil {
    panic("final log")
}
ctx := jsonmap
v := jsonmap["vendors"]
vs := v.([]interface{})
vendors := make([]map[string]interface{}, 0, len(vs))
for _, v := range vs {
    m := v.(map[string]interface{})
    vendors = append(vendors, m)
}
jsonmap["vendors"] = vendors

doc := xlst.new()
err = doc.readtemplate("export_support_template.xlsx")
if err != nil {
    fmt.println("error opening the template: ", err)
    panic("error opening template")
}
err = doc.render(ctx)
if err != nil {
    fmt.println("error rendering the template: ", err)
    panic("error rendering template")
}
err = doc.save("report.xlsx")

如您所见,我刚刚对数据进行了整理。然后,我专注于 vendors 键,将其转换为切片 (v.([]inteface{}),创建一个 []map[string]interface{} 类型的新变量,并复制数据转换在这个简单循环中映射切片的元素:

for _, v := range vs {
    m := v.(map[string]interface{})
    vendors = append(vendors, m)
}

然后,我只是在原始地图中重新分配了 vendors 键 (ctx["vendors"] = sellers),并将其传递到模板中。只需要不到 10 行代码,一切都非常顺利。不需要反思,或任何其他魔法。只是简单的类型转换。如果没有这个,您使用的软件包确实会抱怨在 interface{} 类型上使用 range (而不是首先检查密钥是否可以成功转换为 []interface{})。我从主文件中删除了所有其他函数(示例数据除外),运行 go build 并执行 ./json_marshaller。它毫无问题地生成了正确填充的 xlsx 文件。简单。

我仍然建议您实际为模板程序包创建一个 pr,这样您就不必自己处理这些东西。这应该是一个相当简单的改变,但就目前而言:这种方法效果非常好。

包含您的模板

查看 go-xlsx-templater 的存储库,我真的不明白为什么您需要除此之外的任何内容:

data := map[string]interface{}
_ = json.unmarshal(input, &data)

之所以完全没问题,是因为 go-xlsx-templater 已经执行了该映射中所有 interface{} 值的类型转换。它将遍历地图,并检查是否有任何值属于 []interface{} 类型,然后迭代此切片并检查切片内的 map[string]interface{} 值。它是 all in the source code。根据他们自己的文档,如果您的模板看起来有点像这样,它应该可以正常工作:

| total:              | {{ totalamount }}  |
| subtotal:           | {{ subtotal }}     |
|                     | vendor subtotal    | fees                       | order                       |
| {{ range vendors }} |
|                     |                    | {{ methodsoftender.fees }} | {{ methodsoftender.order }} |
|                     | {{ subtotalfees }} |
| {{ end }}           |

您最终应该正确填充电子表格:

| total:    | 4               |      |       |
| subtotal: | 4               |      |       |
|           | vendor subtotal | fees | order |
|           |                 | 2    | 1     |
|           |                 | 2    | 1     |
|           | 4               |      |       |
|           |                 | 2    | 1     |
|           |                 | 1    | 1     |
|           | 3               |      |       |

本质上,您显示的数据集与存储库在其自己的 main README file 中提供的示例没有什么不同。上下文数据本身包含嵌套地图以及显示如何使用它的电子表格模板的屏幕截图。说白了,我觉得你的问题几乎就是rtfm的一个例子......

但是,如果没有您的模板,就无法确定,所以我将在下面完整地留下我最初的答案...

如果我正确理解您的要求,您正在寻找一种使用类型断言/强制转换从 map[string]interface{} 类型的映射中提取键并使用 [ 类型的值]接口{}[]map[字符串]接口{}。在此特定示例中,您需要可以通过 []map[string]interface{}(而不是 interface{}[]interface{})访问顶级密钥 vendors。如果这就是您正在寻找的,您可以很容易地做到这一点:

data := map[string]interface{}{}
if err := json.unmarshal(jsonbytes, &data); err != nil {
    // handle error
}
nested := map[string][]map[string]interface{}{} // subset of data that needs to be accessible as slices of maps
for k, v := range data {
    if s, ok := v.([]interface); ok {
        // this key is of type []interface, see if we can use it as slice of maps
        if maps, err := sliceanytomap(s); err == nil {
            nested[k] = maps
        } // handle error if needed
    }
}

func sliceanytomap(s []any) ([]map[string]interface{}, error) {
    ret := make([]map[string]interface{}, 0, len(s))
    for _, v := range s {
        if m, ok := v.(map[string]interface{}); ok {
            ret = append(ret, m)
        } else {
            return nil, errors.new("slice contains non-map data")
        }
    }
    return ret, nil
}

这样,您最终会得到一个名为 nested 的变量,其中包含 vendors 之类的键,并且可以通过 []map[string]interface{} 访问数据。这可以传递到您的模板程序,以使用供应商数据填充电子表格。

Demo here

现在,在 vendors 的一些键中,有一些数据又是一个地图,但仍然无法直接访问。将这个 sliceanytomap 内容分解为一个函数将使您更轻松地转换您需要的所有数据。

说了这么多,我个人确实认为这是一个 x-y 问题。您在此处显示的数据看起来具有非常明确的结构。如果您使用实际类型,那么与其胡乱使用 map[string]interface{} 之类的东西,那么阅读、编写和维护会容易得多。根据您在问题中包含的内容,只需使用以下几种类型即可:

type data struct {
    total    float64  `json:"totalamount"`
    subtotal float64  `json:"subtotal"`
    vendors  []vendor `json:"vendors"`
}

type vendor struct {
    methodoftenders []mot   `json:"methodoftenders"`
    subtotalfees    float64 `json:"subtotalfees"`
}

type mot struct {
    fees  float64 `json:"fees"`
    order float64 `json:"order"`
}

使用这些类型,您可以快速、轻松地将给定的输入解析为更易于后续使用的格式:

data := data{}
if err := json.unmarshal(input, &data); err != nil {
    // handle error
}
for _, vendor := range data.vendors {
    fmt.printf("vendor subtotal fees: %f\n", vendor.subtotalfees)
    for i, mot := range vendor.methodoftenders {
        fmt.printf("mot %d:\norder: %f\nfees: %f\n\n", i+1, mot.order, mot.fees)
    }
    fmt.println("-----")
}

Demo here

最后几件事我想知道是否所有数值都应该float64。当然,这对于金额和/或费用来说是有意义的(假设它们是货币值),但是 order 字段可能包含一个订单号,我不希望它是像 0.123 这样的值。

最后,如果您需要将此数据传递给需要映射的模板器,那么使用上述类型仍然是一件相当简单的事情。有几种方法可以做到这一点。最简单的方法是:

func (d data) tomap() (map[string]interface{}, error) {
    raw, err := json.marshal(d)
    if err != nil {
        return nil, err
    }
    asmap := map[string]interface{}{}
    if err := json.unmarshal(raw, &asmap); err != nil {
        return nil, err
    }
    return asmap, nil
}

json 编组实现起来很快,但效率有点低,所以如果性能很重要,那么您可以花几分钟编写一些方法,例如:

func (d data) tomap() map[string]interface{} {
    vendors := make([]map[string]interface{}, 0, len(d.vendors))
    for _, v := range d.vendors {
        vendors = append(vendors, v.tomap())
    }
    return map[string]interface{}{
        "totalamount": d.total,
        "subtotal":    d.subtotal,
        "vendors":     vendors,
    }
}

func (v vendor) tomap() map[string]interface{} {
    mot := make([]map[string]interface{}, 0, len(v.methodsoftender))
    for _, m := range v.methodsoftender {
        mot = append(mot, m.tomap())
    }
    return map[string]interface{
        "methodsoftender": mot,
        "subtotalfees":    v.subtotalfees,
    }
}

func (m mot) tomap() map[string]interface{} {
    return map[string]interface{}{
        "fees":  m.fees,
        "order": m.order,
    }
}

然后,将 data 对象转换为地图非常简单:

dataMap := data.ToMap()

本篇关于《将大括号{}转换为键值对{}》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于Golang的相关知识,请关注golang学习网公众号!

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
用Java的公钥进行RSA解密操作用Java的公钥进行RSA解密操作
上一篇
用Java的公钥进行RSA解密操作
ECDSA私钥的解密方法是怎样的?
下一篇
ECDSA私钥的解密方法是怎样的?
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • 毕业宝AIGC检测:AI生成内容检测工具,助力学术诚信
    毕业宝AIGC检测
    毕业宝AIGC检测是“毕业宝”平台的AI生成内容检测工具,专为学术场景设计,帮助用户初步判断文本的原创性和AI参与度。通过与知网、维普数据库联动,提供全面检测结果,适用于学生、研究者、教育工作者及内容创作者。
    18次使用
  • AI Make Song:零门槛AI音乐创作平台,助你轻松制作个性化音乐
    AI Make Song
    AI Make Song是一款革命性的AI音乐生成平台,提供文本和歌词转音乐的双模式输入,支持多语言及商业友好版权体系。无论你是音乐爱好者、内容创作者还是广告从业者,都能在这里实现“用文字创造音乐”的梦想。平台已生成超百万首原创音乐,覆盖全球20个国家,用户满意度高达95%。
    29次使用
  • SongGenerator.io:零门槛AI音乐生成器,快速创作高质量音乐
    SongGenerator
    探索SongGenerator.io,零门槛、全免费的AI音乐生成器。无需注册,通过简单文本输入即可生成多风格音乐,适用于内容创作者、音乐爱好者和教育工作者。日均生成量超10万次,全球50国家用户信赖。
    27次使用
  •  BeArt AI换脸:免费在线工具,轻松实现照片、视频、GIF换脸
    BeArt AI换脸
    探索BeArt AI换脸工具,免费在线使用,无需下载软件,即可对照片、视频和GIF进行高质量换脸。体验快速、流畅、无水印的换脸效果,适用于娱乐创作、影视制作、广告营销等多种场景。
    30次使用
  • SEO标题协启动:AI驱动的智能对话与内容生成平台 - 提升创作效率
    协启动
    SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
    32次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码