質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.24%

  • Go

    353questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Goのreflect.valueのEnum()で参照している変数のポインタ取得方法について

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 311

 前提・実現したいこと

reflectの動きを理解すること。その手段として、次のようなお題を立てました。

<お題>
{"key1": []} というJSONをjson.unmarshalでinterface[]に取り込み、その結果を引数として受け取り、{"key1": [“update”]} に変更する関数「Add」を作成する。

 発生している問題・エラーメッセージ

下記のコードの※1の箇所で次のエラーが発生します。

panic: reflect: reflect.Value.Set using unaddressable value

 該当のソースコード

package main

import (
    "fmt"
    "encoding/json"
    "reflect"
)

func main() {
    strDst := `{"key1": []}`
    strAdd := `"update"`
    var objDst interface{}
    var objAdd interface{}
    json.Unmarshal(([]byte)(strDst), &objDst)    
    json.Unmarshal(([]byte)(strAdd), &objAdd)    
    Add(&objDst, objAdd)
}

func Add(dst interface{}, add interface{}){
    v := reflect.ValueOf(dst).Elem() // Merge()で宣言したobjDstのポインタ
    v = v.Elem().MapIndex(reflect.ValueOf("key1"))   // ※2  多段構成に対応するため
    fmt.Println(v.Kind(), v.Elem().Kind())  // それぞれinterface, slice
    fmt.Println(v.Elem().Len())   // 0と表示される
    if v.Elem().Len() == 0 {
        sr := reflect.ValueOf(reflect.ValueOf(add))
        v.Set(reflect.Append(v.Elem(), sr)) // ※1
    }
    fmt.Println(v)
    // 以下省略
}

以下の質問に対するご回答としてmattnさんから頂いたコードをベースとしております。
https://teratail.com/questions/106644

 試したこと

コードの ※2 の行をコメントアウトすれば、以下のようなシンプルな追加処理を行えることを確認しました。

成功したケースの例:
JSON [] に対する “update” の追加(追加の結果は[update])

https://blog.golang.org/laws-of-reflection にも書かれている通り、ポインタに対するreflect.valueであればSetが機能するため、期待通り動作するのだと思います(関数Addはポインタを引数で受け取っている) しかし、多段構成のJSONに対応するには ※2 のような処理でObjectから配列を取り出す必要がありますが、この処理で得られるvはポインタではないため、Setが機能しないものと理解しています。

 質問

上記のようなケースにおいて、Setを実行するためにアドレス可能な値を得るにはどのようにすればよいでしょうか? あるいは配列に値を追加するSet以外の方法があれば、そちらでも構いません。お手数ですが、よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

reflectを使わずに書いたコードを意識して書くと良いでしょう。

https://play.golang.org/p/jVZBBJlTytJ

package main

import (
    "fmt"
)

func main() {
    objDst := map[string]interface{}{
        "key1": []interface{}{},
    }
    objAdd := "update"
    Add(objDst, objAdd)
    fmt.Printf("%#v\n", objDst)
}

func Add(dst interface{}, value interface{}) {
    m := dst.(map[string]interface{})
    s := m["key1"].([]interface{})
    m["key1"] = append(s, value) //#1
}

#1でm["key1"]にアサインをやり直していることがわかると思います。
この行で「s = append(s, value)」を書いても新たなsが生まれるだけで元のdstObjに反映はされません。

なので#1に相当するreflect実装を書く必要があります。

動くように修正した例は以下です。
https://play.golang.org/p/iftGeiOMTTU

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

func main() {
    strDst := `{"key1": []}`
    strAdd := `"update"`
    var objDst interface{}
    var objAdd interface{}
    json.Unmarshal(([]byte)(strDst), &objDst)
    json.Unmarshal(([]byte)(strAdd), &objAdd)
    Add(&objDst, objAdd)
}

func Add(dst interface{}, add interface{}) {
    m := reflect.ValueOf(dst).Elem()               // Merge()で宣言したobjDstのポインタ
    v := m.Elem().MapIndex(reflect.ValueOf("key1")) // ※2  多段構成に対応するため
    fmt.Println(v.Kind(), v.Elem().Kind())         // それぞれinterface, slice
    fmt.Println(v.Elem().Len())                    // 0と表示される
    if v.Elem().Len() == 0 {
        sr := reflect.ValueOf(reflect.ValueOf(add))
        m.Elem().SetMapIndex(reflect.ValueOf("key1"), reflect.Append(v.Elem(), sr)) // ※1
    }
    fmt.Println(v)
    // 以下省略
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/29 21:57

    ちなみにGoに慣れた人はreflectを使わず書いた事例のように書きます。reflectはトラブルシュートが難しいし極力使うのを避けるくらいの認識のほうが良いかと思います。

    キャンセル

  • 2018/01/07 18:21

    年末年始で返信が遅くなり恐縮です。ご丁寧なご説明ありがとうございました。頂いた2つの例についてよく理解することができました。前回の質問で分かったつもりでいましたが、いざ自分でコードを書いてみるとまた似たようなことにハマってしまいました。改めて頭を整理してみます。
    また、補足して頂いた通り、たしかにreflectは保守の面で問題が多そうなので、理解できたとしても、その使い方には気をつけたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.24%

関連した質問

同じタグがついた質問を見る

  • Go

    353questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。