Go で構造化されていない JSON データを変更する必要があったことはありますか? パスワードとブラックリストに登録されたすべてのフィールドを削除したり、キーの名前をcamelCase
からsnake_case
に変更したり、JavaScript がint64
好まないためにすべての数値 ID を文字列に変換したりしなければならなかったのではないでしょうか? これまでの解決策が、 encoding/json
使用してすべてをmap[string]any
にアンマーシャリングしてから、それを元に戻すことだったとしたら... 正直に言って、それは効率的とは程遠いものです!
JSON データをループし、各項目のパスを取得し、その場で正確に何を行うかを決定できるとしたらどうでしょうか?
はい、良いニュースがあります。Go 1.23 の新しいイテレータ機能により、JSON を反復処理して操作するより優れた方法があります。Go で JSON を操作するための強力で効率的なツール、 ezpkg.io/iter.jsonをご紹介します。
alice.jsonファイルがあるとします。
{ "name": "Alice", "age": 24, "scores": [9, 10, 8], "address": { "city": "The Sun", "zip": 10101 } }
まず、 for range Parse()
使用して JSON ファイルを反復処理し、各項目のパス、キー、トークン、レベルを出力します。examples /01.iterを参照してください。
package main import ( "fmt" "ezpkg.io/errorz" iterjson "ezpkg.io/iter.json" ) func main() { data := `{"name": "Alice", "age": 24, "scores": [9, 10, 8], "address": {"city": "The Sun", "zip": 10101}}` // 🎄Example: iterate over json fmt.Printf("| %12v | %10v | %10v |%v|\n", "PATH", "KEY", "TOKEN", "LVL") fmt.Println("| ------------ | ---------- | ---------- | - |") for item, err := range iterjson.Parse([]byte(data)) { errorz.MustZ(err) fmt.Printf("| %12v | %10v | %10v | %v |\n", item.GetPathString(), item.Key, item.Token, item.Level) } }
コードは次のように出力します:
| PATH | KEY | TOKEN |LVL| | ------------ | ---------- | ---------- | - | | | | { | 0 | | name | "name" | "Alice" | 1 | | age | "age" | 24 | 1 | | scores | "scores" | [ | 1 | | scores.0 | | 9 | 2 | | scores.1 | | 10 | 2 | | scores.2 | | 8 | 2 | | scores | | ] | 1 | | address | "address" | { | 1 | | address.city | "city" | "The Sun" | 2 | | address.zip | "zip" | 10101 | 2 | | address | | } | 1 | | | | } | 0 |
Builder
を使用して JSON データを構築します。インデント用のオプションの引数を受け入れます。examples /02.builderを参照してください。
b := iterjson.NewBuilder("", " ") // open an object b.Add("", iterjson.TokenObjectOpen) // add a few fields b.Add("name", "Alice") b.Add("age", 22) b.Add("email", "[email protected]") b.Add("phone", "(+84) 123-456-789") // open an array b.Add("languages", iterjson.TokenArrayOpen) b.Add("", "English") b.Add("", "Vietnamese") b.Add("", iterjson.TokenArrayClose) // close the array // accept any type that can marshal to json b.Add("address", Address{ HouseNumber: 42, Street: "Ly Thuong Kiet", City: "Ha Noi", Country: "Vietnam", }) // accept []byte as raw json b.Add("pets", []byte(`[{"type":"cat","name":"Kitty","age":2},{"type":"dog","name":"Yummy","age":3}]`)) // close the object b.Add("", iterjson.TokenObjectClose) out := errorz.Must(b.Bytes()) fmt.Printf("\n--- build json ---\n%s\n", out)
インデント付きの JSON が出力されます:
{ "name": "Alice", "age": 22, "email": "[email protected]", "phone": "(+84) 123-456-789", "languages": [ "English", "Vietnamese" ], "address": {"house_number":42,"street":"Ly Thuong Kiet","city":"Ha Noi","country":"Vietnam"}, "pets": [ { "type": "cat", "name": "Kitty", "age": 2 }, { "type": "dog", "name": "Yummy", "age": 3 } ] }
JSON データのキーと値をBuilder
に送信することで、JSON データを再構築またはフォーマットできます。examples /03.reformatを参照してください。
{ // 🐝Example: minify json b := iterjson.NewBuilder("", "") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) b.AddRaw(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- minify ---\n%s\n----------\n", out) } { // 🦋Example: format json b := iterjson.NewBuilder("👉 ", "\t") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) b.AddRaw(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- reformat ---\n%s\n----------\n", out) }
最初の例では JSON を縮小し、2 番目の例では各行にプレフィックス「👉」を付けてフォーマットします。
--- minify --- {"name":"Alice","age":24,"scores":[9,10,8],"address":{"city":"The Sun","zip":10101}} ---------- --- reformat --- 👉 { 👉 "name": "Alice", 👉 "age": 24, 👉 "scores": [ 👉 9, 👉 10, 👉 8 👉 ], 👉 "address": { 👉 "city": "The Sun", 👉 "zip": 10101 👉 } 👉 } ----------
この例では、 fmt.Fprintf()
呼び出しの前にb.WriteNewline()
を追加して、JSON 出力に行番号を追加します。examples /04.line_numberを参照してください。
// 🐞Example: print with line number i := 0 b := iterjson.NewBuilder("", " ") for item, err := range iterjson.Parse(data) { i++ errorz.MustZ(err) b.WriteNewline(item.Token.Type()) // 👉 add line number fmt.Fprintf(b, "%3d ", i) b.Add(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- line number ---\n%s\n----------\n", out)
出力は次のようになります:
1 { 2 "name": "Alice", 3 "age": 24, 4 "scores": [ 5 9, 6 10, 7 8 8 ], 9 "address": { 10 "city": "The Sun", 11 "zip": 10101 12 } 13 }
b.WriteComma()
とb.WriteNewline()
の間にfmt.Fprintf(comment)
を置くことで、各行の末尾にコメントを追加できます。examples /05.commentを参照してください。
i, newlineIdx, maxIdx := 0, 0, 30 b := iterjson.NewBuilder("", " ") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) b.WriteComma(item.Token.Type()) // 👉 add comment if i > 0 { length := b.Len() - newlineIdx fmt.Fprint(b, strings.Repeat(" ", maxIdx-length)) fmt.Fprintf(b, "// %2d", i) } i++ b.WriteNewline(item.Token.Type()) newlineIdx = b.Len() // save the newline index b.Add(item.Key, item.Token) } length := b.Len() - newlineIdx fmt.Fprint(b, strings.Repeat(" ", maxIdx-length)) fmt.Fprintf(b, "// %2d", i) out := errorz.Must(b.Bytes()) fmt.Printf("\n--- comment ---\n%s\n----------\n", out)
出力は次のようになります:
{ // 1 "name": "Alice", // 2 "age": 24, // 3 "scores": [ // 4 9, // 5 10, // 6 8 // 7 ], // 8 "address": { // 9 "city": "The Sun", // 10 "zip": 10101 // 11 } // 12 } // 13
現在のアイテムのパスを取得するには、 item.GetPathString()
とitem.GetRawPath()
を使用します。これらを使用して JSON データをフィルターできます。examples /06.filter_printを参照してください。
item.GetPathString()
とregexp
使用した例:
fmt.Printf("\n--- filter: GetPathString() ---\n") i := 0 for item, err := range iterjson.Parse(data) { i++ errorz.MustZ(err) path := item.GetPathString() switch { case path == "name", strings.Contains(path, "address"): // continue default: continue } // 👉 print with line number fmt.Printf("%2d %20s . %s\n", i, item.Token, item.GetPath()) }
item.GetRawPath()
とpath.Match()
を使用した例:
fmt.Printf("\n--- filter: GetRawPath() ---\n") i := 0 for item, err := range iterjson.Parse(data) { i++ errorz.MustZ(err) path := item.GetRawPath() switch { case path.Match("name"), path.Contains("address"): // continue default: continue } // 👉 print with line number fmt.Printf("%2d %20s . %s\n", i, item.Token, item.GetPath()) }
どちらの例も出力は次のようになります。
2 "Alice" . name 9 { . address 10 "The Sun" . address.city 11 10101 . address.zip 12 } . address
Builder
オプションSetSkipEmptyStructures(false)
とフィルタリング ロジックと組み合わせることで、JSON データをフィルタリングし、新しい JSON を返すことができます。examples /07.filter_jsonを参照してください。
// 🦁Example: filter and output json b := iterjson.NewBuilder("", " ") b.SetSkipEmptyStructures(true) // 👉 skip empty [] or {} for item, err := range iterjson.Parse(data) { errorz.MustZ(err) if item.Token.IsOpen() || item.Token.IsClose() { b.Add(item.Key, item.Token) continue } path := item.GetPathString() switch { case path == "name", strings.Contains(path, "address"): // continue default: continue } b.Add(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- filter: output json ---\n%s\n----------\n", out)
この例では、フィルタリングされたフィールドのみを含む新しい JSON が返されます。
{ "name": "Alice", "address": { "city": "The Sun", "zip": 10101 } }
これは JSON データの値を編集する例です。API に数値 ID を使用していると仮定します。ID が大きすぎるため、JavaScript では処理できません。文字列に変換する必要があります。examples /08.number_idおよびorder.jsonを参照してください。
JSON データを反復処理し、すべての_id
フィールドを見つけて、数値 ID を文字列に変換します。
b := iterjson.NewBuilder("", " ") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) key, _ := item.GetRawPath().Last().ObjectKey() if strings.HasSuffix(key, "_id") { id, err0 := item.Token.GetInt() if err0 == nil { b.Add(item.Key, strconv.Itoa(id)) continue } } b.Add(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- convert number id ---\n%s\n----------\n", out)
これにより、数値 ID に引用符が追加されます。
{ "order_id": "12345678901234", "number": 12, "customer_id": "12345678905678", "items": [ { "item_id": "12345678901042", "quantity": 1, "price": 123.45 }, { "item_id": "12345678901098", "quantity": 2, "price": 234.56 } ] }
ezpkg.io/iter.jsonパッケージにより、Go 開発者は JSON データを正確かつ効率的に処理できるようになります。複雑な JSON 構造を反復処理したり、新しい JSON オブジェクトを動的に構築したり、データをフォーマットまたは縮小したり、特定のフィールドをフィルター処理したり、値を変換したりする必要がある場合でも、 iter.json は柔軟で強力なソリューションを提供します。
データを完全に解析する必要なく、JSON を効果的に操作するためのツールとして、このパッケージをコミュニティと共有できることを嬉しく思います。まだ開発の初期段階であり、機能を追加する余地はありますが、すでに多くの一般的なユースケースでうまく機能しています。
具体的なご要望や改善のアイデアがありましたら、お気軽にご連絡ください。皆様のフィードバックを聞き、ユースケースのサポートをさせていただきます。🥳
私は Oliver Nguyen です。Go と JS を扱うソフトウェア エンジニアです。毎日、学習して自分自身が成長していくのを楽しんでいます。時々、新しいオープン ソース プロジェクトを立ち上げます。旅の途中で知識や考えを共有します。
この投稿は以下でも公開されています。