Repository: ktr0731/evans (go)

Issue:
Add support to display the default values.
## Describe the bug
Evans doesn't offer the possibility to display the default values (something similar to -emit-defaults in grpccurl).

## To reproduce
View the response fields which were set to their default value . Those fields will not be visible at all in evans console, as the grpc doesn't return them , per grpc specs: https://developers.google.com/protocol-buffers/docs/proto3#json

## Expected behavior
default fields should me made available in the evans console, on demand

## Screenshots
N/A

## Environment
All

## Additional context
Issue opened based on this discussion: https://github.com/ktr0731/evans/discussions/403#discussioncomment-578171


Pull request description:
add --emit-defaults option
Resolves #404. 

Evans now supports `--emit-defaults` option which emits default values.

``` sh
$ echo '{}' | ./evans -r cli call api.Example.Unary
{}

$ echo '{}' | ./evans -r cli call --emit-defaults api.Example.Unary
{
  "message": ""
}
```

``` sh
api.Example@127.0.0.1:50051> call Unary
name (TYPE_STRING) =>
{}

api.Example@127.0.0.1:50051> call --emit-defaults Unary
name (TYPE_STRING) =>
{
  "message": ""
}
```

Patch:
diff --git a/app/cli_commands.go b/app/cli_commands.go
index 466e115..afd713e 100644
--- a/app/cli_commands.go
+++ b/app/cli_commands.go
@@ -11,8 +11,9 @@ import (
 
 func newCLICallCommand(flags *flags, ui cui.UI) *cobra.Command {
 	var (
-		out    string
-		enrich bool
+		out          string
+		enrich       bool
+		emitDefaults bool
 	)
 	cmd := &cobra.Command{
 		Use:     "call [options ...] <method>",
@@ -34,7 +35,13 @@ func newCLICallCommand(flags *flags, ui cui.UI) *cobra.Command {
 			if len(args) == 0 {
 				return errors.New("method is required")
 			}
-			invoker, err := mode.NewCallCLIInvoker(ui, args[0], cfg.file, cfg.Config.Request.Header, enrich, out)
+			invoker, err := mode.NewCallCLIInvoker(ui, args[0], &mode.CallCLIInvokerOption{
+				Headers:      cfg.Config.Request.Header,
+				Enrich:       enrich,
+				EmitDefaults: emitDefaults,
+				FilePath:     cfg.file,
+				FormatType:   out,
+			})
 			if err != nil {
 				return err
 			}
@@ -50,6 +57,7 @@ func newCLICallCommand(flags *flags, ui cui.UI) *cobra.Command {
 	f := cmd.Flags()
 	initFlagSet(f, ui.Writer())
 	f.BoolVar(&enrich, "enrich", false, `enrich response output includes header, message, trailer and status`)
+	f.BoolVar(&emitDefaults, "emit-defaults", false, `render fields with default values`)
 	f.StringVarP(&out, "output", "o", "curl", `output format. one of "json" or "curl". "curl" is a curl-like format.`)
 
 	cmd.SetHelpFunc(usageFunc(ui.Writer(), []string{"file"}))
diff --git a/app/commands.go b/app/commands.go
index 5748d2d..82406f3 100644
--- a/app/commands.go
+++ b/app/commands.go
@@ -113,7 +113,10 @@ func newOldCommand(flags *flags, ui cui.UI) *command {
 			if cfg.repl || !isCLIMode {
 				return runREPLCommand(cfg, ui)
 			}
-			invoker, err := mode.NewCallCLIInvoker(ui, cfg.call, cfg.file, cfg.Config.Request.Header, false, "")
+			invoker, err := mode.NewCallCLIInvoker(ui, cfg.call, &mode.CallCLIInvokerOption{
+				Headers:  cfg.Config.Request.Header,
+				FilePath: cfg.file,
+			})
 			if err != nil {
 				return err
 			}
@@ -207,7 +210,10 @@ func newCLICommand(flags *flags, ui cui.UI) *cobra.Command {
 				}
 				call = args[0]
 			}
-			invoker, err := mode.NewCallCLIInvoker(ui, call, cfg.file, cfg.Config.Request.Header, false, "")
+			invoker, err := mode.NewCallCLIInvoker(ui, call, &mode.CallCLIInvokerOption{
+				Headers:  cfg.Config.Request.Header,
+				FilePath: cfg.file,
+			})
 			if err != nil {
 				return err
 			}
diff --git a/format/curl/curl.go b/format/curl/curl.go
index 9987de1..ca70b3a 100644
--- a/format/curl/curl.go
+++ b/format/curl/curl.go
@@ -31,11 +31,13 @@ type responseFormatter struct {
 	wroteHeader, wroteMessage, wroteTrailer bool
 }
 
-func NewResponseFormatter(w io.Writer) format.ResponseFormatterInterface {
+func NewResponseFormatter(w io.Writer, emitDefaults bool) format.ResponseFormatterInterface {
 	return &responseFormatter{
-		w:           w,
-		json:        json.NewPresenter("  "),
-		pbMarshaler: &jsonpb.Marshaler{},
+		w:    w,
+		json: json.NewPresenter("  "),
+		pbMarshaler: &jsonpb.Marshaler{
+			EmitDefaults: emitDefaults,
+		},
 	}
 }
 
@@ -58,7 +60,13 @@ func (p *responseFormatter) FormatMessage(v interface{}) error {
 	if p.wroteHeader {
 		fmt.Fprintf(p.w, "\n")
 	}
-	msg, err := p.json.Format(v)
+
+	m, err := p.convertProtoMessageToMap(v.(proto.Message))
+	if err != nil {
+		return err
+	}
+
+	msg, err := p.json.Format(m)
 	if err != nil {
 		return err
 	}
diff --git a/format/json/json.go b/format/json/json.go
index cd60d88..0b81fe4 100644
--- a/format/json/json.go
+++ b/format/json/json.go
@@ -36,8 +36,10 @@ type responseFormatter struct {
 	pbMarshaler *jsonpb.Marshaler
 }
 
-func NewResponseFormatter(w io.Writer) format.ResponseFormatterInterface {
-	return &responseFormatter{w: w, p: json.NewPresenter("  "), pbMarshaler: &jsonpb.Marshaler{}}
+func NewResponseFormatter(w io.Writer, emitDefaults bool) format.ResponseFormatterInterface {
+	return &responseFormatter{w: w, p: json.NewPresenter("  "), pbMarshaler: &jsonpb.Marshaler{
+		EmitDefaults: emitDefaults,
+	}}
 }
 
 func (p *responseFormatter) FormatHeader(header metadata.MD) {
diff --git a/go.mod b/go.mod
index 79bf2dc..1fe126a 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/ktr0731/go-prompt v0.2.2-0.20190609072126-7894cc3f2925
 	github.com/ktr0731/go-shellstring v0.1.3
 	github.com/ktr0731/go-updater v0.1.5
-	github.com/ktr0731/grpc-test v0.1.7
+	github.com/ktr0731/grpc-test v0.1.8
 	github.com/ktr0731/grpc-web-go-client v0.2.7
 	github.com/lunixbochs/vtclean v1.0.0 // indirect
 	github.com/manifoldco/promptui v0.8.0
diff --git a/go.sum b/go.sum
index ff58476..7d2c177 100644
--- a/go.sum
+++ b/go.sum
@@ -248,6 +248,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
@@ -334,7 +335,6 @@ github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -367,6 +367,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
@@ -630,8 +631,8 @@ github.com/ktr0731/go-shellstring v0.1.3/go.mod h1:f0/XX0hsm6A02Es/UDQ9MGd3VqpCH
 github.com/ktr0731/go-updater v0.1.5 h1:AiaQxJoI0OTGqvsJYdIITCOaEzQQOqU2MHKUwttVShY=
 github.com/ktr0731/go-updater v0.1.5/go.mod h1:dsdOg7a9sj6ttcOU5ZxPCtKdm9WeB1hwcX/7oKgz9/0=
 github.com/ktr0731/grpc-test v0.1.4/go.mod h1:v47616grayBYXQveGWxO3OwjLB3nEEnHsZuMTc73FM0=
-github.com/ktr0731/grpc-test v0.1.7 h1:QwjxlKMHb1FzQMoXjjK1F2HDdIf5f72WXWZs4He8nNI=
-github.com/ktr0731/grpc-test v0.1.7/go.mod h1:vjMPOfGerucM+ysAKiynHDS6IUSErB6O9gBOHWyUs5s=
+github.com/ktr0731/grpc-test v0.1.8 h1:ZGX8bQu9aIlJFJ4NBUBvcyyzyFa2xH5STfmZZWoP4tc=
+github.com/ktr0731/grpc-test v0.1.8/go.mod h1:NfrFy0MifkU4tftOx0iZirhLHjz8+dAOKJQ5XRiYjDg=
 github.com/ktr0731/grpc-web-go-client v0.2.7 h1:Op1U1XC29kb7S8XptqYd742JwcG7oR5Q7QoYPZMN2T4=
 github.com/ktr0731/grpc-web-go-client v0.2.7/go.mod h1:1Iac8gFJvC/DRfZoGnFZsfEbEq/wQFK+2Ve1o3pHkCQ=
 github.com/ktr0731/modfile v1.11.2/go.mod h1:LzNwnHJWHbuDh3BO17lIqzqDldXqGu1HCydWH3SinE0=
@@ -1433,8 +1434,10 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
 google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/mode/cli.go b/mode/cli.go
index 9a74e42..1a62d38 100644
--- a/mode/cli.go
+++ b/mode/cli.go
@@ -29,16 +29,23 @@ var DefaultCLIReader io.Reader = os.Stdin
 // CLIInvoker represents an invokable function for CLI mode.
 type CLIInvoker func(context.Context) error
 
+type CallCLIInvokerOption struct {
+	Headers      config.Header
+	Enrich       bool
+	EmitDefaults bool
+	FilePath     string // If empty, the invoker tries to read input from stdin.
+	FormatType   string
+}
+
 // NewCallCLIInvoker returns an CLIInvoker implementation for calling RPCs.
-// If filePath is empty, the invoker tries to read input from stdin.
-func NewCallCLIInvoker(ui cui.UI, methodName, filePath string, headers config.Header, enrich bool, formatType string) (CLIInvoker, error) {
+func NewCallCLIInvoker(ui cui.UI, methodName string, opt *CallCLIInvokerOption) (CLIInvoker, error) {
 	if methodName == "" {
 		return nil, errors.New("method is required")
 	}
 	return func(ctx context.Context) error {
 		in := DefaultCLIReader
-		if filePath != "" {
-			f, err := os.Open(filePath)
+		if opt.FilePath != "" {
+			f, err := os.Open(opt.FilePath)
 			if err != nil {
 				return errors.Wrap(err, "failed to open the script file")
 			}
@@ -47,20 +54,20 @@ func NewCallCLIInvoker(ui cui.UI, methodName, filePath string, headers config.He
 		}
 		filler := fill.NewSilentFiller(in)
 		var rfi format.ResponseFormatterInterface
-		switch formatType {
+		switch opt.FormatType {
 		case "curl":
-			rfi = curl.NewResponseFormatter(ui.Writer())
+			rfi = curl.NewResponseFormatter(ui.Writer(), opt.EmitDefaults)
 		case "json":
-			rfi = fmtjson.NewResponseFormatter(ui.Writer())
+			rfi = fmtjson.NewResponseFormatter(ui.Writer(), opt.EmitDefaults)
 		default:
-			rfi = curl.NewResponseFormatter(ui.Writer())
+			rfi = curl.NewResponseFormatter(ui.Writer(), opt.EmitDefaults)
 		}
 		usecase.InjectPartially(usecase.Dependencies{
-			ResponseFormatter: format.NewResponseFormatter(rfi, enrich),
+			ResponseFormatter: format.NewResponseFormatter(rfi, opt.Enrich),
 			Filler:            filler,
 		})
 
-		for k, v := range headers {
+		for k, v := range opt.Headers {
 			for _, vv := range v {
 				usecase.AddHeader(k, vv)
 			}
diff --git a/repl/commands.go b/repl/commands.go
index 1ab1f34..1fa773c 100644
--- a/repl/commands.go
+++ b/repl/commands.go
@@ -155,7 +155,7 @@ func (c *showCommand) Run(w io.Writer, args []string) error {
 }
 
 type callCommand struct {
-	enrich, digManually, bytesFromFile bool
+	enrich, digManually, bytesFromFile, emitDefaults bool
 }
 
 func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) {
@@ -164,6 +164,7 @@ func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) {
 	fs.BoolVar(&c.enrich, "enrich", false, "enrich response output includes header, message, trailer and status")
 	fs.BoolVar(&c.digManually, "dig-manually", false, "prompt asks whether to dig down if it encountered to a message field")
 	fs.BoolVar(&c.bytesFromFile, "bytes-from-file", false, "interpret TYPE_BYTES input as a relative path to a file")
+	fs.BoolVar(&c.emitDefaults, "emit-defaults", false, "render fields with default values")
 	return fs, true
 }
 
@@ -192,7 +193,7 @@ func (c *callCommand) Validate(args []string) error {
 func (c *callCommand) Run(w io.Writer, args []string) error {
 	usecase.InjectPartially(
 		usecase.Dependencies{
-			ResponseFormatter: format.NewResponseFormatter(curl.NewResponseFormatter(w), c.enrich),
+			ResponseFormatter: format.NewResponseFormatter(curl.NewResponseFormatter(w, c.emitDefaults), c.enrich),
 		},
 	)
 


Test patch:
diff --git a/e2e/cli_test.go b/e2e/cli_test.go
index 2d18d79..236ae04 100644
--- a/e2e/cli_test.go
+++ b/e2e/cli_test.go
@@ -161,39 +161,39 @@ func TestE2E_CLI(t *testing.T) {
 			cmd:             "call",
 			args:            "--file testdata/unary_call.in Unary",
 			deprecatedUsage: true,
-			expectedOut:     `{ "message": "hello, oumae" }`,
+			expectedOut:     `{ "message": "oumae" }`,
 		},
 		"call unary RPC with an input file": {
 			commonFlags: "--proto testdata/test.proto",
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call fully-qualified unary RPC with an input file": {
 			commonFlags: "--proto testdata/test.proto",
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC with --call flag (backward-compatibility)": {
 			commonFlags:     "--package api --service Example --proto testdata/test.proto",
 			cmd:             "",
 			args:            "--file testdata/unary_call.in --call Unary",
 			deprecatedUsage: true,
-			expectedOut:     `{ "message": "hello, oumae" }`,
+			expectedOut:     `{ "message": "oumae" }`,
 		},
 		"call unary RPC without package name because the size of packages is 1 (backward-compatibility)": {
 			commonFlags:     "--service Example --proto testdata/test.proto",
 			cmd:             "call",
 			args:            "--file testdata/unary_call.in Unary",
 			deprecatedUsage: true,
-			expectedOut:     `{ "message": "hello, oumae" }`,
+			expectedOut:     `{ "message": "oumae" }`,
 		},
 		"call unary RPC without package and service name because the size of packages and services are 1": {
 			commonFlags: "--proto testdata/test.proto",
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in Unary",
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC with an input reader": {
 			commonFlags: "--proto testdata/test.proto",
@@ -206,7 +206,13 @@ func TestE2E_CLI(t *testing.T) {
 					mode.DefaultCLIReader = old
 				}
 			},
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
+		},
+		"call unary RPC with --emit-defaults": {
+			commonFlags: "--proto testdata/test.proto",
+			cmd:         "call",
+			args:        "--emit-defaults --file testdata/unary_empty_call.in api.Example.Unary",
+			expectedOut: `{ "message": "" }`,
 		},
 		"call client streaming RPC": {
 			commonFlags: "--proto testdata/test.proto",
@@ -271,7 +277,7 @@ func TestE2E_CLI(t *testing.T) {
 			commonFlags: "--header grpc-timeout=1s --proto testdata/test.proto",
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"client streaming call timed out": {
 			commonFlags:  "--header grpc-timeout=0s --proto testdata/test.proto",
@@ -344,14 +350,14 @@ func TestE2E_CLI(t *testing.T) {
 			args:            "--file testdata/unary_call.in Unary",
 			reflection:      true,
 			deprecatedUsage: true,
-			expectedOut:     `{ "message": "hello, oumae" }`,
+			expectedOut:     `{ "message": "oumae" }`,
 		},
 		"call unary RPC with reflection with an input file": {
 			commonFlags: "--reflection",
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
 			reflection:  true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 
 		// call command with TLS
@@ -389,7 +395,7 @@ func TestE2E_CLI(t *testing.T) {
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
 			tls:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"cannot launch with TLS and reflection because server didn't enable TLS": {
 			commonFlags:  "--tls -r --host localhost --cacert testdata/rootCA.pem",
@@ -405,14 +411,14 @@ func TestE2E_CLI(t *testing.T) {
 			args:        "--file testdata/unary_call.in api.Example.Unary",
 			tls:         true,
 			reflection:  true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC with TLS and --servername": {
 			commonFlags: "--tls --servername localhost --cacert testdata/rootCA.pem --proto testdata/test.proto",
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
 			tls:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"cannot launch with mutual TLS auth because --certkey is missing": {
 			commonFlags:  "--tls --host localhost --cacert testdata/rootCA.pem --cert testdata/localhost.pem --proto testdata/test.proto",
@@ -433,7 +439,7 @@ func TestE2E_CLI(t *testing.T) {
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
 			tls:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 
 		// call command with gRPC-Web
@@ -450,7 +456,7 @@ func TestE2E_CLI(t *testing.T) {
 			cmd:         "call",
 			args:        "--file testdata/unary_call.in api.Example.Unary",
 			web:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC with an input file and reflection against to gRPC-Web server": {
 			commonFlags: "--web -r",
@@ -458,7 +464,7 @@ func TestE2E_CLI(t *testing.T) {
 			args:        "--file testdata/unary_call.in api.Example.Unary",
 			web:         true,
 			reflection:  true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call client streaming RPC against to gRPC-Web server": {
 			commonFlags: "--web --proto testdata/test.proto",
diff --git a/e2e/old_cli_test.go b/e2e/old_cli_test.go
index 48369b8..13445a2 100644
--- a/e2e/old_cli_test.go
+++ b/e2e/old_cli_test.go
@@ -116,15 +116,15 @@ func TestE2E_OldCLI(t *testing.T) {
 		},
 		"call unary RPC with an input file by CLI mode": {
 			args:        "--package api --service Example --call Unary --file testdata/unary_call.in testdata/test.proto",
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC without package name because the size of packages is 1": {
 			args:        "--service Example --call Unary --file testdata/unary_call.in testdata/test.proto",
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC without package and service name because the size of packages and services are 1": {
 			args:        "--call Unary --file testdata/unary_call.in testdata/test.proto",
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC with an input reader by CLI mode": {
 			args: "--package api --service Example --call Unary testdata/test.proto",
@@ -135,7 +135,7 @@ func TestE2E_OldCLI(t *testing.T) {
 					mode.DefaultCLIReader = old
 				}
 			},
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call client streaming RPC by CLI mode": {
 			args:        "--package api --service Example --call ClientStreaming --file testdata/client_streaming.in testdata/test.proto",
@@ -195,7 +195,7 @@ func TestE2E_OldCLI(t *testing.T) {
 		"call unary RPC by CLI mode with reflection with an input file": {
 			args:        "--reflection --package api --service Example --call Unary --file testdata/unary_call.in",
 			reflection:  true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 
 		// CLI mode with TLS
@@ -223,7 +223,7 @@ func TestE2E_OldCLI(t *testing.T) {
 		"call unary RPC with TLS by CLI mode": {
 			args:        "--tls --host localhost --cacert testdata/rootCA.pem --service Example --call Unary --file testdata/unary_call.in testdata/test.proto",
 			tls:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"cannot launch CLI mode with TLS and reflection by CLI mode because server didn't enable TLS": {
 			args:         "--tls -r --host localhost --cacert testdata/rootCA.pem --service Example --call Unary --file testdata/unary_call.in",
@@ -235,12 +235,12 @@ func TestE2E_OldCLI(t *testing.T) {
 			args:        "--tls -r --host localhost --cacert testdata/rootCA.pem --service Example --call Unary --file testdata/unary_call.in",
 			tls:         true,
 			reflection:  true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC with TLS and --servername by CLI mode": {
 			args:        "--tls --servername localhost --cacert testdata/rootCA.pem --service Example --call Unary --file testdata/unary_call.in testdata/test.proto",
 			tls:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"cannot launch CLI mode with mutual TLS auth because --certkey is missing": {
 			args:         "--tls --host localhost --cacert testdata/rootCA.pem --cert testdata/localhost.pem --service Example --call Unary --file testdata/unary_call.in testdata/test.proto",
@@ -255,7 +255,7 @@ func TestE2E_OldCLI(t *testing.T) {
 		"call unary RPC with mutual TLS auth by CLI mode": {
 			args:        "--tls --host localhost --cacert testdata/rootCA.pem --cert testdata/localhost.pem --certkey testdata/localhost-key.pem --service Example --call Unary --file testdata/unary_call.in testdata/test.proto",
 			tls:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 
 		// CLI mode with gRPC-Web
@@ -268,13 +268,13 @@ func TestE2E_OldCLI(t *testing.T) {
 		"call unary RPC with an input file by CLI mode against to gRPC-Web server": {
 			args:        "--web --package api --service Example --call Unary --file testdata/unary_call.in testdata/test.proto testdata/test.proto",
 			web:         true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call unary RPC with an input file by CLI mode and reflection against to gRPC-Web server": {
 			args:        "--web -r --service Example --call Unary --file testdata/unary_call.in",
 			web:         true,
 			reflection:  true,
-			expectedOut: `{ "message": "hello, oumae" }`,
+			expectedOut: `{ "message": "oumae" }`,
 		},
 		"call client streaming RPC by CLI mode against to gRPC-Web server": {
 			args:        "--web --service Example --call ClientStreaming --file testdata/client_streaming.in testdata/test.proto",
diff --git a/e2e/repl_test.go b/e2e/repl_test.go
index 1599588..e53bf12 100644
--- a/e2e/repl_test.go
+++ b/e2e/repl_test.go
@@ -84,6 +84,10 @@ func TestE2E_REPL(t *testing.T) {
 			commonFlags: "--service Example --proto testdata/test.proto",
 			input:       []interface{}{"call Unary", "kaguya"},
 		},
+		"call Unary with --emit-defaults": {
+			commonFlags: "--proto testdata/test.proto",
+			input:       []interface{}{"package api", "service Example", "call --emit-defaults Unary", ""},
+		},
 		"call ClientStreaming": {
 			commonFlags: "--proto testdata/test.proto",
 			// io.EOF means end of inputting.
diff --git a/e2e/testdata/fixtures/teste2e_cli-print_call_command_usage.golden b/e2e/testdata/fixtures/teste2e_cli-print_call_command_usage.golden
index 21a6351..2a897ba 100644
--- a/e2e/testdata/fixtures/teste2e_cli-print_call_command_usage.golden
+++ b/e2e/testdata/fixtures/teste2e_cli-print_call_command_usage.golden
@@ -12,6 +12,7 @@ Examples:
 
 Options:
         --enrich                   enrich response output includes header, message, trailer and status (default "false")
+        --emit-defaults            render fields with default values (default "false")
         --output, -o string        output format. one of "json" or "curl". "curl" is a curl-like format. (default "curl")
         --file, -f string          a script file that will be executed by (used only CLI mode)
         --help, -h                 display help text and exit (default "false")
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_only_service.golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_only_service.golden
index ac30482..92088c5 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_only_service.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_only_service.golden
@@ -1,5 +1,5 @@
 
 {
-  "message": "hello, kaguya"
+  "message": "kaguya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_package_and_service.golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_package_and_service.golden
index 8dbdc72..6921a50 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_package_and_service.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_selecting_package_and_service.golden
@@ -1,6 +1,6 @@
 
 
 {
-  "message": "hello, kaguya"
+  "message": "kaguya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_specifying_--service.golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_specifying_--service.golden
index 3cbedde..9e44568 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_specifying_--service.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unary_by_specifying_--service.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, kaguya"
+  "message": "kaguya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unarymessage.golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unarymessage.golden
index c735239..4dab53f 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-call_unarymessage.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unarymessage.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, kaguya shinomiya"
+  "message": "kaguya shinomiya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryoneof.golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryoneof.golden
index 01f8951..93e97fa 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryoneof.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryoneof.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, ai hayasaka"
+  "message": "ai hayasaka"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryrepeated.golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryrepeated.golden
index 000101d..86c7e65 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryrepeated.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryrepeated.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, miyuki, kaguya, chika, yu"
+  "message": "miyuki, kaguya, chika, yu"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden b/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden
index 5915f4e..311847d 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden
@@ -1,4 +1,2 @@
-{
-  "message": "hello, "
-}
+{}
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message.golden b/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message.golden
index c7406f6..b9ca073 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, mumei "
+  "message": "mumei "
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden b/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden
index e2ca8fa..fc23446 100644
--- a/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, kanade hisaishi, kumiko "
+  "message": "kanade hisaishi, kumiko "
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
index 3de0726..1486230 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
@@ -3,5 +3,6 @@ usage: call <method name>
 Options:
       --bytes-from-file   interpret TYPE_BYTES input as a relative path to a file
       --dig-manually      prompt asks whether to dig down if it encountered to a message field
+      --emit-defaults     render fields with default values
       --enrich            enrich response output includes header, message, trailer and status
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_only_service.golden b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_only_service.golden
index ac30482..92088c5 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_only_service.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_only_service.golden
@@ -1,5 +1,5 @@
 
 {
-  "message": "hello, kaguya"
+  "message": "kaguya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service.golden b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service.golden
index 8dbdc72..6921a50 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service.golden
@@ -1,6 +1,6 @@
 
 
 {
-  "message": "hello, kaguya"
+  "message": "kaguya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service_with_enriched_output.golden b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service_with_enriched_output.golden
index e5a671b..e71deeb 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service_with_enriched_output.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_selecting_package_and_service_with_enriched_output.golden
@@ -5,7 +5,7 @@ header_key1: header_val1
 header_key2: header_val2
 
 {
-  "message": "hello, kaguya"
+  "message": "kaguya"
 }
 
 trailer_key1: trailer_val1
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_specifying_--service.golden b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_specifying_--service.golden
index 3cbedde..9e44568 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_unary_by_specifying_--service.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unary_by_specifying_--service.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, kaguya"
+  "message": "kaguya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unary_with_--emit-defaults.golden b/e2e/testdata/fixtures/teste2e_repl-call_unary_with_--emit-defaults.golden
new file mode 100644
index 0000000..83f34f7
--- /dev/null
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unary_with_--emit-defaults.golden
@@ -0,0 +1,6 @@
+
+
+{
+  "message": ""
+}
+
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unarymessage.golden b/e2e/testdata/fixtures/teste2e_repl-call_unarymessage.golden
index c735239..4dab53f 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_unarymessage.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unarymessage.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, kaguya shinomiya"
+  "message": "kaguya shinomiya"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unaryoneof.golden b/e2e/testdata/fixtures/teste2e_repl-call_unaryoneof.golden
index 01f8951..93e97fa 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_unaryoneof.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unaryoneof.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, ai hayasaka"
+  "message": "ai hayasaka"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unaryrepeated.golden b/e2e/testdata/fixtures/teste2e_repl-call_unaryrepeated.golden
index 000101d..86c7e65 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_unaryrepeated.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unaryrepeated.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, miyuki, kaguya, chika, yu"
+  "message": "miyuki, kaguya, chika, yu"
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden b/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden
index 5915f4e..311847d 100644
--- a/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_fields_if_there_are_no_message_type_fields.golden
@@ -1,4 +1,2 @@
-{
-  "message": "hello, "
-}
+{}
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message.golden b/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message.golden
index c7406f6..b9ca073 100644
--- a/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, mumei "
+  "message": "mumei "
 }
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden b/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden
index e2ca8fa..fc23446 100644
--- a/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-ctrl-c_skips_the_rest_of_the_current_message_and_exits_the_repeated_field.golden
@@ -1,4 +1,4 @@
 {
-  "message": "hello, kanade hisaishi, kumiko "
+  "message": "kanade hisaishi, kumiko "
 }
 
diff --git a/e2e/testdata/unary_empty_call.in b/e2e/testdata/unary_empty_call.in
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/e2e/testdata/unary_empty_call.in
@@ -0,0 +1,1 @@
+{}