GoでJSONを使用する場合、空フィールドと未設定フィールドの区別方法

4592 ワード

微信は「呉親強の深夜食堂」を検索し、公衆番号に注目し、「微信」に返信し、友达と一緒に勉強した.
数週間前、JSONデータのCRUDサポートを追加する必要があるGolangベースのマイクロサービスプロジェクトを開発していました.
通常、定義されたすべてのフィールドとomitemptyプロパティを含むエンティティの構造を構築します.
type Article struct {
   Id   string      `json:"id"`
   Name string      `json:"name,omitempty"`
   Desc string      `json:"desc,omitempty"`
}

しかし、この表現は特にUpdateまたはEdit操作に深刻な問題をもたらす.
例えば、更新要求のJSONがそうであるとすると、
{"id":"1234","name":"xyz","desc":""}

空のdescフィールドに注意してください.では、Goでどのように分解されているかを見てみましょう.
func Test_JSON1(t *testing.T) {         
   jsonData:=`{"id":"1234","name":"xyz","desc":""}`
   req:=Article{}
   _=json.Unmarshal([]byte(jsonData),&req)
   fmt.Printf("%+v",req)
}
Output:
=== RUN   Test_JSON1
{Id:1234 Name:xyz Desc:}

ここのdescは空の文字列です.明らかに、クライアントはdescを空の文字列に設定することを望んでいます.これは私たちのプログラムによって推定されます.
ただし、クライアントがdescの既存値を変更したくない場合は、desc文字列を再送信するのは正しくありません.したがって、要求されたJSONデータは、
{"id":"1234","name":"xyz"}

私たちの構造に分解し
func Test_JSON2(t *testing.T) {         
   jsonData:=`{"id":"1234","name":"xyz"}`
   req:=Article{}
   _=json.Unmarshal([]byte(jsonData),&req)
   fmt.Printf("%+v",req)
}
Output:
=== RUN   Test_JSON2
{Id:1234 Name:xyz Desc:}

私たちは依然としてdescを空の文字列としていますが、未設定フィールドと空のフィールドをどのように区別しますか?
短い答えはポインタです.
ソリューションというインスピレーションは、go-githubなどの既存のGolangライブラリから来ています.構造体フィールドをポインタタイプに変更できます.
type Article struct {
   Id    string      `json:"id"`
   Name *string      `json:"name,omitempty"`
   Desc *string      `json:"desc,omitempty"`
}

これにより、フィールドに追加のステータスを追加しました.このフィールドが元の要求JSONデータに存在しない場合、null(nil)となります.
一方、フィールドが確かに存在し、値が空の場合、ポインタは空ではなく、フィールドには空の値が含まれます.
注意-Idはポインタタイプに変更されていません.そのため、空のステータスを持つことはできません.IdはデータベースIdと同様に常に存在する必要があります.やってみましょう.
func Test_JSON_Empty(t *testing.T) {
   jsonData := `{"id":"1234","name":"xyz","desc":""}`
   req := Article{}
   _ = json.Unmarshal([]byte(jsonData), &req)
   fmt.Printf("%+vn", req)
   fmt.Printf("%sn", *req.Name)
   fmt.Printf("%sn", *req.Desc)
}
func Test_JSON_Nil(t *testing.T) {
   jsonData := `{"id":"1234","name":"xyz"}`
   req := Article{}
   _ = json.Unmarshal([]byte(jsonData), &req)
   fmt.Printf("%+vn", req)
   fmt.Printf("%sn", *req.Name)
}

出力、
=== RUN   Test_JSON_Empty
{Id:1234 Name:0xc000088540 Desc:0xc000088550}
Name: xyz
Desc: 
--- PASS: Test_JSON_Empty (0.00s)
=== RUN   Test_JSON_Nil
{Id:1234 Name:0xc00005c590 Desc:}
Name: xyz
--- PASS: Test_JSON_Nil (0.00s)

第1の場合、descが空の文字列に設定されると、空でないポインタが得られ、その値は空の文字列である.2つ目の場合、フィールドが設定されていない場合、空のポインタが得られます.
そのため、この2つの更新を区別することができます.この方法は文字列タイプだけでなくint,ネストstructなど,他のタイプにも適用できる.
しかし、この方法はいくつかの問題をもたらします.
≪空のセキュリティ|Null Security|emdw≫:ポインタ以外のデータ型には固有の空のセキュリティがあります.これは、stringまたはintがGolangでnullになることはありません.常にデフォルト値があります.ただし、ポインタが定義されると、手動で設定されていない場合、これらのデータ型はnullにデフォルト設定されます.したがって、空性を検証せずにこれらのポインタデータにアクセスしようとすると、アプリケーションがクラッシュする可能性があります.
#The following code will crash because desc is null
func Test_JSON_Nil(t *testing.T) {
   jsonData := `{"id":"1234","name":"xyz"}`
   req := Article{}
   _ = json.Unmarshal([]byte(jsonData), &req)
   fmt.Printf("%+vn", req)
   fmt.Printf("%sn", *req.Desc)
}

空のポインタを常にチェックすることで、この問題は簡単に回避できますが、コードが優雅に見えない可能性があります.
印刷性:ご覧のように、ポインタの値は印刷されません.逆に、16進数ポインタが印刷されるので、アプリケーションではあまり役に立ちません.これはstringerインタフェースを実現することによって克服できる.
func (a *Article) String() string {
   output:=fmt.Sprintf("Id: %s ",a.Id)
   if a.Name!=nil{
   output+=fmt.Sprintf("Name: '%s' ",*a.Name)
   }
   if a.Desc!=nil{
   output+=fmt.Sprintf("Desc: '%s' ",a.Desc)
   }
   return output
}

付録のもう1つの解決策は、ポインタを気にすることなく、外部ライブラリを使用して空のタイプを持つことです.これらのタイプは、ポインタが空であるかどうかを確認する方法を提供します.
https://github.com/guregu/null
https://github.com/google/go-github
実用的ではないでしょうか.他に解決策はありますか?以下のメッセージを歓迎します.
もし文章があなたに役に立つならば、いいね、転送、伝言はすべて1種の支持です!