教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang自定义结构体转map

Golang自定义结构体转map

发布时间:2022-12-09   编辑:jiaochengji.com
教程集为您提供Golang自定义结构体转map等资源,欢迎您收藏本站,我们将为您提供最新的Golang自定义结构体转map资源

在Golang中,如何将一个结构体转成map? 本文介绍两种方法。第一种是是使用<code>json</code>包解析解码编码。第二种是使用反射,使用反射的效率比较高,代码在这里。如果觉得代码有用,可以给我的代码仓库一个star。

假设有下面的一个结构体

<pre><code class="hljs go" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">newUser</span><span class="hljs-params">()</span> <span class="hljs-title">User</span></span> { name := <span class="hljs-string">"user"</span> MyGithub := GithubPage{ URL: <span class="hljs-string">"https://github.com/liangyaopei"</span>, Star: <span class="hljs-number">1</span>, } NoDive := StructNoDive{NoDive: <span class="hljs-number">1</span>} dateStr := <span class="hljs-string">"2020-07-21 12:00:00"</span> date, _ := time.Parse(timeLayout, dateStr) profile := Profile{ Experience: <span class="hljs-string">"my experience"</span>, Date: date, } <span class="hljs-keyword">return</span> User{ Name: name, Github: MyGithub, NoDive: NoDive, MyProfile: profile, } } <span class="hljs-keyword">type</span> User <span class="hljs-keyword">struct</span> { Name <span class="hljs-keyword">string</span> <span class="hljs-string">`map:"name,omitempty"`</span> <span class="hljs-comment">// string</span> Github GithubPage <span class="hljs-string">`map:"github,dive,omitempty"`</span> <span class="hljs-comment">// struct dive</span> NoDive StructNoDive <span class="hljs-string">`map:"no_dive,omitempty"`</span> <span class="hljs-comment">// no dive struct</span> MyProfile Profile <span class="hljs-string">`map:"my_profile,omitempty"`</span> <span class="hljs-comment">// struct implements its own method</span> } <span class="hljs-keyword">type</span> GithubPage <span class="hljs-keyword">struct</span> { URL <span class="hljs-keyword">string</span> <span class="hljs-string">`map:"url"`</span> Star <span class="hljs-keyword">int</span> <span class="hljs-string">`map:"star"`</span> } <span class="hljs-keyword">type</span> StructNoDive <span class="hljs-keyword">struct</span> { NoDive <span class="hljs-keyword">int</span> } <span class="hljs-keyword">type</span> Profile <span class="hljs-keyword">struct</span> { Experience <span class="hljs-keyword">string</span> <span class="hljs-string">`map:"experience"`</span> Date time.Time <span class="hljs-string">`map:"time"`</span> } <span class="hljs-comment">// its own toMap method</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p Profile)</span> <span class="hljs-title">StructToMap</span><span class="hljs-params">()</span> <span class="hljs-params">(key <span class="hljs-keyword">string</span>, value <span class="hljs-keyword">interface</span>{})</span></span> { <span class="hljs-keyword">return</span> <span class="hljs-string">"time"</span>, p.Date.Format(timeLayout) } </code></pre><h2 class="heading">json包的marshal,unmarshal</h2>

先将结构体序列化成<code>[]byte</code>数组,再从<code>[]byte</code>数组序列化成结构体。

<pre><code class="hljs go" lang="go">data, _ := json.Marshal(&user) m := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}) json.Unmarshal(data, &m) </code></pre>

优势

<ul> <li>使用简单 劣势</li> <li>效率比较慢</li> <li>不能支持一些定制的键,也不能支持一些定制的方法,例如将struct的域展开等。</li> </ul> <h2 class="heading">使用反射</h2>

本文实现了使用反射将结构体转成<code>map</code>的方法。通过标签(tag)和反射,将上文示例的<code>newUser()</code>返回的结果转化成下面的一个<code>map</code>。其中包含struct的域的展开,定制化struct的方法。

<pre><code class="hljs go" lang="go"><span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}{ <span class="hljs-string">"name"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"no_dive"</span>: StructNoDive{NoDive: <span class="hljs-number">1</span>}, <span class="hljs-comment">// dive struct field</span> <span class="hljs-string">"url"</span>: <span class="hljs-string">"https://github.com/liangyaopei"</span>, <span class="hljs-string">"star"</span>: <span class="hljs-number">1</span>, <span class="hljs-comment">// customized method</span> <span class="hljs-string">"time"</span>: <span class="hljs-string">"2020-07-21 12:00:00"</span>, } </code></pre><h3 class="heading">实现思路 & 源码解析</h3> <h4 class="heading">1.标签识别。</h4>

使用<code>readTag</code>方法读取域(field)的标签,如果没有标签,使用域的名字。然后读取tag中的选项。目前支持3个选项

<ul> <li>'-':忽略当前这个域</li> <li>'omitempty' : 当这个域的值为空,忽略这个域</li> <li>'dive' : 递归地遍历这个结构体,将所有字段作为键</li> </ul>

如果选中了一个选项,就讲这个域对应的二进制位置为1.。

<pre><code class="hljs go" lang="go"><span class="hljs-keyword">const</span> ( OptIgnore = <span class="hljs-string">"-"</span> OptOmitempty = <span class="hljs-string">"omitempty"</span> OptDive = <span class="hljs-string">"dive"</span> ) <span class="hljs-keyword">const</span> ( flagIgnore = <span class="hljs-number">1</span> << <span class="hljs-literal">iota</span> flagOmiEmpty flagDive ) <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">readTag</span><span class="hljs-params">(f reflect.StructField, tag <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, <span class="hljs-keyword">int</span>)</span></span> { val, ok := f.Tag.Lookup(tag) fieldTag := <span class="hljs-string">""</span> flag := <span class="hljs-number">0</span> <span class="hljs-comment">// no tag, use field name</span> <span class="hljs-keyword">if</span> !ok { <span class="hljs-keyword">return</span> f.Name, flag } opts := strings.Split(val, <span class="hljs-string">","</span>) fieldTag = opts[<span class="hljs-number">0</span>] <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i < <span class="hljs-built_in">len</span>(opts); i { <span class="hljs-keyword">switch</span> opts[i] { <span class="hljs-keyword">case</span> OptIgnore: flag |= flagIgnore <span class="hljs-keyword">case</span> OptOmitempty: flag |= flagOmiEmpty <span class="hljs-keyword">case</span> OptDive: flag |= flagDive } } <span class="hljs-keyword">return</span> fieldTag, flag } </code></pre><h4 class="heading">2.结构体的域(field)的遍历。</h4>

遍历结构体的每一个域(field),判断field的类型(kind)。如果是<code>string</code>,<code>int</code>等的基本类型,直接取值,并且把标签中的值作为key。

<pre><code class="hljs go" lang="go"><span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < t.NumField(); i { ... <span class="hljs-keyword">switch</span> fieldValue.Kind() { <span class="hljs-keyword">case</span> reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: res[tagVal] = fieldValue.Int() <span class="hljs-keyword">case</span> reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: res[tagVal] = fieldValue.Uint() <span class="hljs-keyword">case</span> reflect.Float32, reflect.Float64: res[tagVal] = fieldValue.Float() <span class="hljs-keyword">case</span> reflect.String: res[tagVal] = fieldValue.String() <span class="hljs-keyword">case</span> reflect.Bool: res[tagVal] = fieldValue.Bool() <span class="hljs-keyword">default</span>: } } } </code></pre><h4 class="heading">3.内嵌结构体的转换</h4>

如果是结构体,先检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,就递归地调用<code>StructToMap</code>方法,然后根据是否展开(<code>dive</code>),来把返回结果写入res的map。

<pre><code class="hljs go" lang="go"><span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < t.NumField(); i { fieldType := t.Field(i) <span class="hljs-comment">// ignore unexported field</span> <span class="hljs-keyword">if</span> fieldType.PkgPath != <span class="hljs-string">""</span> { <span class="hljs-keyword">continue</span> } <span class="hljs-comment">// read tag</span> tagVal, flag := readTag(fieldType, tag) <span class="hljs-keyword">if</span> flag&flagIgnore != <span class="hljs-number">0</span> { <span class="hljs-keyword">continue</span> } fieldValue := v.Field(i) <span class="hljs-keyword">if</span> flag&flagOmiEmpty != <span class="hljs-number">0</span> && fieldValue.IsZero() { <span class="hljs-keyword">continue</span> } <span class="hljs-comment">// ignore nil pointer in field</span> <span class="hljs-keyword">if</span> fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { <span class="hljs-keyword">continue</span> } <span class="hljs-keyword">if</span> fieldValue.Kind() == reflect.Ptr { fieldValue = fieldValue.Elem() } <span class="hljs-comment">// get kind</span> <span class="hljs-keyword">switch</span> fieldValue.Kind() { <span class="hljs-keyword">case</span> reflect.Struct: _, ok := fieldValue.Type().MethodByName(methodName) <span class="hljs-keyword">if</span> ok { key, value, err := callFunc(fieldValue, methodName) <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err } res[key] = value <span class="hljs-keyword">continue</span> } <span class="hljs-comment">// recursive</span> deepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName) <span class="hljs-keyword">if</span> deepErr != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, deepErr } <span class="hljs-keyword">if</span> flag&flagDive != <span class="hljs-number">0</span> { <span class="hljs-keyword">for</span> k, v := <span class="hljs-keyword">range</span> deepRes { res[k] = v } } <span class="hljs-keyword">else</span> { res[tagVal] = deepRes } <span class="hljs-keyword">default</span>: } } ... } <span class="hljs-comment">// call function</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">callFunc</span><span class="hljs-params">(fv reflect.Value, methodName <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, <span class="hljs-keyword">interface</span>{}, error)</span></span> { methodRes := fv.MethodByName(methodName).Call([]reflect.Value{}) <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(methodRes) != methodResNum { <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"wrong method %s, should have 2 output: (string,interface{})"</span>, methodName) } <span class="hljs-keyword">if</span> methodRes[<span class="hljs-number">0</span>].Kind() != reflect.String { <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"wrong method %s, first output should be string"</span>, methodName) } key := methodRes[<span class="hljs-number">0</span>].String() <span class="hljs-keyword">return</span> key, methodRes[<span class="hljs-number">1</span>], <span class="hljs-literal">nil</span> } </code></pre><h4 class="heading">4.array,slice类型的转换</h4>

如果是<code>array</code>,<code>slice</code>类型,类似地,检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,将这个field的tag作为key,域的值作为value。

<pre><code class="hljs go" lang="go"><span class="hljs-keyword">switch</span> fieldValue.Kind() { <span class="hljs-keyword">case</span> reflect.Slice, reflect.Array: _, ok := fieldValue.Type().MethodByName(methodName) <span class="hljs-keyword">if</span> ok { key, value, err := callFunc(fieldValue, methodName) <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err } res[key] = value <span class="hljs-keyword">continue</span> } res[tagVal] = fieldValue .... } </code></pre><h4 class="heading">5.其他类型</h4>

对于其他类型,例如内嵌的<code>map</code>,直接将其返回结果的值。

<pre><code class="hljs go" lang="go"><span class="hljs-keyword">switch</span> fieldValue.Kind() { ... <span class="hljs-keyword">case</span> reflect.Map: res[tagVal] = fieldValue <span class="hljs-keyword">case</span> reflect.Chan: res[tagVal] = fieldValue <span class="hljs-keyword">case</span> reflect.Interface: res[tagVal] = fieldValue.Interface() <span class="hljs-keyword">default</span>: } </code></pre> 到此这篇关于“Golang自定义结构体转map”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golang Map进行键值自定义排序
golang key map 所有_谨慎使用golang中的map
数据结构和算法(Golang实现)(4)简单入门Golang-结构体和方法
golang key map 所有_golang系列——高级语法之map
golang 自定义Set 及 使用
golang中map的一些注意事项
go语言结构体定义使用
golang-类型变量
golang 并发访问map遇到的问题
Go之URL Query String 编码器和解码器

[关闭]
~ ~