Skip to content

Commit ec4687f

Browse files
committed
cmd/go: allow users to specify required fields in JSON output
For #29666 Change-Id: Ibae3d75bb2c19571c8d473cb47d6c4b3a880bba8 Reviewed-on: https://go-review.googlesource.com/c/go/+/381035 Trust: Michael Matloob <[email protected]> Run-TryBot: Michael Matloob <[email protected]> Reviewed-by: Bryan Mills <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 6c6a8fb commit ec4687f

File tree

3 files changed

+137
-18
lines changed

3 files changed

+137
-18
lines changed

src/cmd/go/alldocs.go

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/list/list.go

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import (
1313
"fmt"
1414
"io"
1515
"os"
16+
"reflect"
1617
"sort"
18+
"strconv"
1719
"strings"
1820
"text/template"
1921

@@ -157,7 +159,10 @@ For more information about the meaning of these fields see the documentation
157159
for the go/build package's Context type.
158160
159161
The -json flag causes the package data to be printed in JSON format
160-
instead of using the template format.
162+
instead of using the template format. The JSON flag can optionally be
163+
provided with a set of comma-separated required field names to be output.
164+
If so, those required fields will always appear in JSON output, but
165+
others may be omitted to save work in computing the JSON struct.
161166
162167
The -compiled flag causes list to set CompiledGoFiles to the Go source
163168
files presented to the compiler. Typically this means that it repeats
@@ -316,29 +321,79 @@ For more about modules, see https://golang.org/ref/mod.
316321
func init() {
317322
CmdList.Run = runList // break init cycle
318323
work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
324+
CmdList.Flag.Var(&listJsonFields, "json", "")
319325
}
320326

321327
var (
322-
listCompiled = CmdList.Flag.Bool("compiled", false, "")
323-
listDeps = CmdList.Flag.Bool("deps", false, "")
324-
listE = CmdList.Flag.Bool("e", false, "")
325-
listExport = CmdList.Flag.Bool("export", false, "")
326-
listFmt = CmdList.Flag.String("f", "", "")
327-
listFind = CmdList.Flag.Bool("find", false, "")
328-
listJson = CmdList.Flag.Bool("json", false, "")
329-
listM = CmdList.Flag.Bool("m", false, "")
330-
listRetracted = CmdList.Flag.Bool("retracted", false, "")
331-
listTest = CmdList.Flag.Bool("test", false, "")
332-
listU = CmdList.Flag.Bool("u", false, "")
333-
listVersions = CmdList.Flag.Bool("versions", false, "")
328+
listCompiled = CmdList.Flag.Bool("compiled", false, "")
329+
listDeps = CmdList.Flag.Bool("deps", false, "")
330+
listE = CmdList.Flag.Bool("e", false, "")
331+
listExport = CmdList.Flag.Bool("export", false, "")
332+
listFmt = CmdList.Flag.String("f", "", "")
333+
listFind = CmdList.Flag.Bool("find", false, "")
334+
listJson bool
335+
listJsonFields jsonFlag // If not empty, only output these fields.
336+
listM = CmdList.Flag.Bool("m", false, "")
337+
listRetracted = CmdList.Flag.Bool("retracted", false, "")
338+
listTest = CmdList.Flag.Bool("test", false, "")
339+
listU = CmdList.Flag.Bool("u", false, "")
340+
listVersions = CmdList.Flag.Bool("versions", false, "")
334341
)
335342

343+
// A StringsFlag is a command-line flag that interprets its argument
344+
// as a space-separated list of possibly-quoted strings.
345+
type jsonFlag map[string]bool
346+
347+
func (v *jsonFlag) Set(s string) error {
348+
if v, err := strconv.ParseBool(s); err == nil {
349+
listJson = v
350+
return nil
351+
}
352+
listJson = true
353+
if *v == nil {
354+
*v = make(map[string]bool)
355+
}
356+
for _, f := range strings.Split(s, ",") {
357+
(*v)[f] = true
358+
}
359+
return nil
360+
}
361+
362+
func (v *jsonFlag) String() string {
363+
var fields []string
364+
for f := range *v {
365+
fields = append(fields, f)
366+
}
367+
sort.Strings(fields)
368+
return strings.Join(fields, ",")
369+
}
370+
371+
func (v *jsonFlag) IsBoolFlag() bool {
372+
return true
373+
}
374+
375+
func (v *jsonFlag) needAll() bool {
376+
return len(*v) == 0
377+
}
378+
379+
func (v *jsonFlag) needAny(fields ...string) bool {
380+
if v.needAll() {
381+
return true
382+
}
383+
for _, f := range fields {
384+
if (*v)[f] {
385+
return true
386+
}
387+
}
388+
return false
389+
}
390+
336391
var nl = []byte{'\n'}
337392

338393
func runList(ctx context.Context, cmd *base.Command, args []string) {
339394
modload.InitWorkfile()
340395

341-
if *listFmt != "" && *listJson == true {
396+
if *listFmt != "" && listJson == true {
342397
base.Fatalf("go list -f cannot be used with -json")
343398
}
344399

@@ -357,9 +412,18 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
357412
}
358413
}
359414

360-
var do func(any)
361-
if *listJson {
415+
var do func(x any)
416+
if listJson {
362417
do = func(x any) {
418+
if !listJsonFields.needAll() {
419+
v := reflect.ValueOf(x).Elem() // do is always called with a non-nil pointer.
420+
// Clear all non-requested fields.
421+
for i := 0; i < v.NumField(); i++ {
422+
if !listJsonFields.needAny(v.Type().Field(i).Name) {
423+
v.Field(i).Set(reflect.Zero(v.Type().Field(i).Type))
424+
}
425+
}
426+
}
363427
b, err := json.MarshalIndent(x, "", "\t")
364428
if err != nil {
365429
out.Flush()
@@ -589,7 +653,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
589653
}
590654

591655
// Do we need to run a build to gather information?
592-
needStale := *listJson || strings.Contains(*listFmt, ".Stale")
656+
needStale := (listJson && listJsonFields.needAny("Stale", "StaleReason")) || strings.Contains(*listFmt, ".Stale")
593657
if needStale || *listExport || *listCompiled {
594658
var b work.Builder
595659
b.Init()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Test using -json flag to specify specific fields.
2+
3+
# Test -json produces "full" output by looking for multiple fields present.
4+
go list -json .
5+
stdout '"Name": "a"'
6+
stdout '"Stale": true'
7+
# Same thing for -json=true
8+
go list -json=true .
9+
stdout '"Name": "a"'
10+
stdout '"Stale": true'
11+
12+
# Test -json=false produces non-json output.
13+
go list -json=false
14+
cmp stdout want-non-json.txt
15+
16+
# Test -json=<field> keeps only that field.
17+
go list -json=Name
18+
cmp stdout want-json-name.txt
19+
20+
# Test -json=<field> with multiple fields.
21+
go list -json=ImportPath,Name,GoFiles,Imports
22+
cmp stdout want-json-multiple.txt
23+
24+
-- go.mod --
25+
module example.com/a
26+
27+
go 1.18
28+
-- a.go --
29+
package a
30+
31+
import "fmt"
32+
33+
func F() {
34+
fmt.Println("hey there")
35+
}
36+
-- want-non-json.txt --
37+
example.com/a
38+
-- want-json-name.txt --
39+
{
40+
"Name": "a"
41+
}
42+
-- want-json-multiple.txt --
43+
{
44+
"ImportPath": "example.com/a",
45+
"Name": "a",
46+
"GoFiles": [
47+
"a.go"
48+
],
49+
"Imports": [
50+
"fmt"
51+
]
52+
}

0 commit comments

Comments
 (0)