Repository: knative/client (go)

Issue:
List command doesn't return empty result in correct format
Hello, 
we are working on a knative plugin for IntelliJ and we found out this misbehavior.

### Bug report

`list` command doesn't return empty result in correct format (json, yaml, .).

```
$ kn service list -o json
No services found.
```

### Expected behavior

I would expect it in json format
E.g 
```
{
    "apiVersion": "serving.knative.dev/v1",
    "items": [],
    "kind": "Service",
    "metadata": {
        ...
    }
}
```

### Steps to reproduce the problem

Step above

### kn version

```
Version:      v0.21.0
Build Date:   2021-02-24 09:16:16
Git Revision: 5f68d612
Supported APIs:
* Serving
  - serving.knative.dev/v1 (knative-serving v0.21.0)
* Eventing
  - sources.knative.dev/v1alpha2 (knative-eventing v0.21.0)
  - eventing.knative.dev/v1beta1 (knative-eventing v0.21.0)
```

### Knative (serving/eventing) version

```
v0.21.0
```


Pull request:
Fix service/revision list output with '-o' param
## Description
This PR fixes behaviour of `service list -o <format>` and `revision list -o <format>` command in case when there are no services/revisions:
```
$ ./kn service list -o json
{
    "apiVersion": "serving.knative.dev/v1",
    "items": [],
    "kind": "ServiceList"
}
```

<!-- Please add a short summary about what the pull request is going to bring on the table -->

## Changes

<!-- Please add list of more detailed changes. These changes should be reflected also in the commit messages -->

* `revision/list.go` now checks `-o` option before type `No revisions found.` 
* `service/list.go` now checks `-o` option before type `No services found.` 

## Reference

<!-- Please add the corresponding issue number which this pull request is about to fix -->
Fixes #1273

<!--
Please add an entry to CHANGELOG.adoc file, too, as part of your Pull Request.

In the following cases, add a short description of PR to the unreleased section in CHANGELOG.adoc:

- 🎁 New feature
- 🐛 Bug fix
- ✨ Feature Update
- 🐣 Refactoring
- 🗑️ Remove feature or internal logic

See other entries in CHANGELOG.adoc as an example for how to add the entry, including a reference to the corresponding issue/PR

PLEASE DON'T ADD THAT LINE HERE IN THE PULL-REQUEST DESCRIPTION BUT DIRECTLY IN CHANGELOG.ADOC AND ADD CHANGELOG.ADOC AS PART OF YOUR PULL-REQUEST.
-->

<!--
To automatically lint go code in this pull request uncomment the line below. You get feedback as comments on your pull request then -->

<!--
/lint
-->


Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 82dbd12f..aee97ec6 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -12,6 +12,16 @@
 | https://github.com/knative/client/pull/[#]
 ////
 
+## (Unreleased)
+[cols="1,10,3", options="header", width="100%"]
+|===
+| | Description | PR
+
+|🐛
+| Respect `-o` in `list` commands in case if no data present
+| https://github.com/knative/client/pull/1276[#1276]
+|===
+
 ## v0.22.0 (2021-04-06)
 [cols="1,10,3", options="header", width="100%"]
 |===
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index 1ab0a9a6..cabdef1c 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -3,17 +3,24 @@
 aliases:
   api-core-wg-leads:
   - dprotaso
+  autoscaling-reviewers:
+  - taragu
   autoscaling-wg-leads:
   - markusthoemmes
   - vagababov
+  autoscaling-writers:
+  - julz
+  - markusthoemmes
+  - vagababov
+  - yanweiguo
   channel-wg-leads:
   - matzew
   - slinkydeveloper
+  client-reviewers:
+  - itsmurugappan
   client-wg-leads:
   - navidshaikh
   - rhuss
-  client-reviewers:
-  - itsmurugappan
   client-writers:
   - dsimansk
   - maximilien
@@ -23,8 +30,22 @@ aliases:
   - tayarani
   conformance-writers:
   - tayarani
+  docs-reviewers:
+  - RichardJJG
+  - jonatasbaldin
+  - mpetason
+  - omerbensaadon
+  - snneji
   docs-wg-leads: []
-  docs-writers: []
+  docs-writers:
+  - abrennan89
+  - carieshmarie
+  - richieescarez
+  eventing-reviewers:
+  - aslom
+  - nlopezgi
+  - tayarani
+  - tommyreddad
   eventing-triage:
   - akashrv
   - antoineco
@@ -39,6 +60,7 @@ aliases:
   - vaikas
   eventing-writers:
   - akashrv
+  - aliok
   - antoineco
   - devguyio
   - grantr
@@ -46,6 +68,7 @@ aliases:
   - lionelvillard
   - matzew
   - n3wscott
+  - pierDipi
   - slinkydeveloper
   - vaikas
   - zhongduo
@@ -98,19 +121,84 @@ aliases:
   - knative-prow-releaser-robot
   - knative-prow-robot
   - knative-test-reporter-robot
+  networking-reviewers:
+  - JRBANCEL
+  - ZhiminXiang
+  - andrew-su
+  - arturenault
+  - markusthoemmes
+  - nak3
+  - shashwathi
+  - tcnghia
+  - vagababov
+  - yanweiguo
   networking-wg-leads:
   - ZhiminXiang
   - nak3
   - tcnghia
+  networking-writers:
+  - JRBANCEL
+  - ZhiminXiang
+  - nak3
+  - tcnghia
+  - vagababov
+  operations-reviewers:
+  - Cynocracy
+  - aliok
+  - houshengbo
+  - jcrossley3
+  - markusthoemmes
+  - matzew
+  - n3wscott
+  - trshafer
   operations-wg-leads:
   - houshengbo
   operations-writers:
+  - Cynocracy
+  - aliok
   - houshengbo
+  - jcrossley3
+  - markusthoemmes
+  - matzew
+  - n3wscott
+  - trshafer
+  pkg-configmap-reviewers:
+  - dprotaso
+  - mattmoor
+  - mdemirhan
+  - vagababov
+  pkg-configmap-writers:
+  - dprotaso
+  - mattmoor
+  - mdemirhan
+  - vagababov
+  pkg-controller-reviewers:
+  - dprotaso
+  - grantr
+  - mattmoor
+  - tcnghia
+  - vagababov
+  - whaught
+  pkg-controller-writers:
+  - dprotaso
+  - grantr
+  - mattmoor
+  - tcnghia
+  - vagababov
+  productivity-reviewers:
+  - coryrc
+  - efiturri
+  - evankanderson
+  - peterfeifanchen
+  - steuhs
+  - yt3liu
   productivity-wg-leads:
   - chizhg
   - n3wscott
   productivity-writers:
+  - chaodaiG
   - chizhg
+  - coryrc
   - n3wscott
   security-wg-leads:
   - evankanderson
@@ -118,11 +206,19 @@ aliases:
   security-writers:
   - evankanderson
   - julz
+  serving-observability-reviewers:
+  - mdemirhan
+  - skonto
+  - yanweiguo
+  serving-observability-writers:
+  - mdemirhan
+  - yanweiguo
+  serving-reviewers:
+  - julz
+  - whaught
   serving-writers:
-  - ZhiminXiang
   - dprotaso
   - markusthoemmes
-  - nak3
   - tcnghia
   - vagababov
   source-wg-leads:
diff --git a/pkg/kn/commands/broker/list.go b/pkg/kn/commands/broker/list.go
index c1fcf548..e8867521 100644
--- a/pkg/kn/commands/broker/list.go
+++ b/pkg/kn/commands/broker/list.go
@@ -61,7 +61,7 @@ func NewBrokerListCommand(p *commands.KnParams) *cobra.Command {
 			if err != nil {
 				return err
 			}
-			if len(brokerList.Items) == 0 {
+			if !brokerListFlags.GenericPrintFlags.OutputFlagSpecified() && len(brokerList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No brokers found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/channel/list.go b/pkg/kn/commands/channel/list.go
index 0146c3bf..ab813717 100644
--- a/pkg/kn/commands/channel/list.go
+++ b/pkg/kn/commands/channel/list.go
@@ -17,10 +17,13 @@ package channel
 import (
 	"fmt"
 
-	"github.com/spf13/cobra"
+	"knative.dev/client/pkg/util"
+	messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
 
+	"github.com/spf13/cobra"
 	"knative.dev/client/pkg/kn/commands"
 	"knative.dev/client/pkg/kn/commands/flags"
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
 )
 
 // NewChannelListCommand is for listing channel objects
@@ -51,7 +54,14 @@ func NewChannelListCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			if channelList == nil || len(channelList.Items) == 0 {
+			if channelList == nil {
+				channelList = &messagingv1.ChannelList{}
+				err := util.UpdateGroupVersionKindWithScheme(channelList, messagingv1.SchemeGroupVersion, scheme.Scheme)
+				if err != nil {
+					return err
+				}
+			}
+			if !listFlags.GenericPrintFlags.OutputFlagSpecified() && len(channelList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No channels found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/channel/list_types.go b/pkg/kn/commands/channel/list_types.go
index 830cfd50..aa75f9ff 100644
--- a/pkg/kn/commands/channel/list_types.go
+++ b/pkg/kn/commands/channel/list_types.go
@@ -62,10 +62,18 @@ func NewChannelListTypesCommand(p *commands.KnParams) *cobra.Command {
 				return knerrors.GetError(err)
 			}
 
-			if channelListTypes == nil || len(channelListTypes.Items) == 0 {
+			if channelListTypes == nil {
+				channelListTypes = &unstructured.UnstructuredList{}
+			}
+			if !listTypesFlags.GenericPrintFlags.OutputFlagSpecified() && len(channelListTypes.Items) == 0 {
 				return fmt.Errorf("no channels found on the backend, please verify the installation")
 			}
 
+			if channelListTypes.GroupVersionKind().Empty() {
+				channelListTypes.SetAPIVersion("apiextensions.k8s.io/v1")
+				channelListTypes.SetKind("CustomResourceDefinitionList")
+			}
+
 			printer, err := listTypesFlags.ToPrinter()
 			if err != nil {
 				return nil
diff --git a/pkg/kn/commands/revision/list.go b/pkg/kn/commands/revision/list.go
index 9bdbe417..2f818b73 100644
--- a/pkg/kn/commands/revision/list.go
+++ b/pkg/kn/commands/revision/list.go
@@ -83,7 +83,7 @@ func NewRevisionListCommand(p *commands.KnParams) *cobra.Command {
 			}
 
 			// Stop if nothing found
-			if len(revisionList.Items) == 0 {
+			if !revisionListFlags.GenericPrintFlags.OutputFlagSpecified() && len(revisionList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No revisions found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/route/list.go b/pkg/kn/commands/route/list.go
index 12710420..2e408963 100644
--- a/pkg/kn/commands/route/list.go
+++ b/pkg/kn/commands/route/list.go
@@ -66,7 +66,7 @@ func NewRouteListCommand(p *commands.KnParams) *cobra.Command {
 			if err != nil {
 				return err
 			}
-			if len(routeList.Items) == 0 {
+			if !routeListFlags.GenericPrintFlags.OutputFlagSpecified() && len(routeList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No routes found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/service/list.go b/pkg/kn/commands/service/list.go
index 32a28caf..30e3a58b 100644
--- a/pkg/kn/commands/service/list.go
+++ b/pkg/kn/commands/service/list.go
@@ -64,7 +64,9 @@ func NewServiceListCommand(p *commands.KnParams) *cobra.Command {
 			if err != nil {
 				return err
 			}
-			if len(serviceList.Items) == 0 {
+
+			// Stop if nothing found
+			if !serviceListFlags.GenericPrintFlags.OutputFlagSpecified() && len(serviceList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No services found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/source/apiserver/list.go b/pkg/kn/commands/source/apiserver/list.go
index b4ff1775..edf701e7 100644
--- a/pkg/kn/commands/source/apiserver/list.go
+++ b/pkg/kn/commands/source/apiserver/list.go
@@ -50,7 +50,7 @@ func NewAPIServerListCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			if len(sourceList.Items) == 0 {
+			if !listFlags.GenericPrintFlags.OutputFlagSpecified() && len(sourceList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No ApiServer source found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/source/binding/list.go b/pkg/kn/commands/source/binding/list.go
index 63c97136..cf8bb023 100644
--- a/pkg/kn/commands/source/binding/list.go
+++ b/pkg/kn/commands/source/binding/list.go
@@ -49,7 +49,7 @@ func NewBindingListCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			if len(sourceList.Items) == 0 {
+			if !listFlags.GenericPrintFlags.OutputFlagSpecified() && len(sourceList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No sink binding found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/source/container/list.go b/pkg/kn/commands/source/container/list.go
index f61be2e6..a28a1d52 100644
--- a/pkg/kn/commands/source/container/list.go
+++ b/pkg/kn/commands/source/container/list.go
@@ -50,7 +50,7 @@ func NewContainerListCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			if len(sourceList.Items) == 0 {
+			if !listFlags.GenericPrintFlags.OutputFlagSpecified() && len(sourceList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No Container source found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/source/list.go b/pkg/kn/commands/source/list.go
index 53e53ed7..1b128057 100644
--- a/pkg/kn/commands/source/list.go
+++ b/pkg/kn/commands/source/list.go
@@ -18,7 +18,8 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
-
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	"knative.dev/client/pkg/dynamic"
 	knerrors "knative.dev/client/pkg/errors"
 	"knative.dev/client/pkg/kn/commands"
@@ -27,6 +28,12 @@ import (
 	sourcesv1alpha2 "knative.dev/client/pkg/sources/v1alpha2"
 )
 
+const (
+	sourceListGroup   = "client.knative.dev"
+	sourceListVersion = "v1alpha1"
+	sourceListKind    = "SourceList"
+)
+
 var listExample = `
   # List available eventing sources
   kn source list
@@ -73,10 +80,17 @@ func NewListCommand(p *commands.KnParams) *cobra.Command {
 				return knerrors.GetError(err)
 			}
 
-			if sourceList == nil || len(sourceList.Items) == 0 {
+			if sourceList == nil {
+				sourceList = &unstructured.UnstructuredList{}
+			}
+			if !listFlags.GenericPrintFlags.OutputFlagSpecified() && len(sourceList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No sources found.\n")
 				return nil
 			}
+
+			if sourceList.GroupVersionKind().Empty() {
+				sourceList.SetGroupVersionKind(schema.GroupVersionKind{Group: sourceListGroup, Version: sourceListVersion, Kind: sourceListKind})
+			}
 			// empty namespace indicates all namespaces flag is specified
 			if namespace == "" {
 				listFlags.EnsureWithNamespace()
diff --git a/pkg/kn/commands/source/list_types.go b/pkg/kn/commands/source/list_types.go
index cca0cf42..34d1089d 100644
--- a/pkg/kn/commands/source/list_types.go
+++ b/pkg/kn/commands/source/list_types.go
@@ -62,10 +62,19 @@ func NewListTypesCommand(p *commands.KnParams) *cobra.Command {
 				return knerrors.GetError(err)
 			}
 
-			if sourceListTypes == nil || len(sourceListTypes.Items) == 0 {
+			if sourceListTypes == nil {
+				sourceListTypes = &unstructured.UnstructuredList{}
+			}
+
+			if !listTypesFlags.GenericPrintFlags.OutputFlagSpecified() && len(sourceListTypes.Items) == 0 {
 				return fmt.Errorf("no sources found on the backend, please verify the installation")
 			}
 
+			if sourceListTypes.GroupVersionKind().Empty() {
+				sourceListTypes.SetAPIVersion("apiextensions.k8s.io/v1")
+				sourceListTypes.SetKind("CustomResourceDefinitionList")
+			}
+
 			printer, err := listTypesFlags.ToPrinter()
 			if err != nil {
 				return nil
diff --git a/pkg/kn/commands/source/ping/list.go b/pkg/kn/commands/source/ping/list.go
index e8edeaa0..9e2118af 100644
--- a/pkg/kn/commands/source/ping/list.go
+++ b/pkg/kn/commands/source/ping/list.go
@@ -50,7 +50,7 @@ func NewPingListCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			if len(sourceList.Items) == 0 {
+			if !listFlags.GenericPrintFlags.OutputFlagSpecified() && len(sourceList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No Ping source found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/subscription/list.go b/pkg/kn/commands/subscription/list.go
index 8a8101bc..d77da374 100644
--- a/pkg/kn/commands/subscription/list.go
+++ b/pkg/kn/commands/subscription/list.go
@@ -19,6 +19,10 @@ package subscription
 import (
 	"fmt"
 
+	"knative.dev/client/pkg/util"
+	messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"github.com/spf13/cobra"
 
 	"knative.dev/client/pkg/kn/commands"
@@ -52,7 +56,14 @@ func NewSubscriptionListCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			if subscriptionList == nil || len(subscriptionList.Items) == 0 {
+			if subscriptionList == nil {
+				subscriptionList = &messagingv1.SubscriptionList{}
+				err := util.UpdateGroupVersionKindWithScheme(subscriptionList, messagingv1.SchemeGroupVersion, scheme.Scheme)
+				if err != nil {
+					return err
+				}
+			}
+			if !listFlags.GenericPrintFlags.OutputFlagSpecified() && len(subscriptionList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No subscriptions found.\n")
 				return nil
 			}
diff --git a/pkg/kn/commands/trigger/list.go b/pkg/kn/commands/trigger/list.go
index ad751a94..7c952a2b 100644
--- a/pkg/kn/commands/trigger/list.go
+++ b/pkg/kn/commands/trigger/list.go
@@ -50,7 +50,7 @@ func NewTriggerListCommand(p *commands.KnParams) *cobra.Command {
 			if err != nil {
 				return err
 			}
-			if len(triggerList.Items) == 0 {
+			if !triggerListFlags.GenericPrintFlags.OutputFlagSpecified() && len(triggerList.Items) == 0 {
 				fmt.Fprintf(cmd.OutOrStdout(), "No triggers found.\n")
 				return nil
 			}


Test patch:
diff --git a/pkg/kn/commands/broker/list_test.go b/pkg/kn/commands/broker/list_test.go
index 73622e91..ad23aabf 100644
--- a/pkg/kn/commands/broker/list_test.go
+++ b/pkg/kn/commands/broker/list_test.go
@@ -61,6 +61,20 @@ func TestBrokerListEmpty(t *testing.T) {
 	eventingRecorder.Validate()
 }
 
+func TestBrokerListEmptyWithJSON(t *testing.T) {
+	eventingClient := clienteventingv1.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+	brokerList := &v1beta1.BrokerList{}
+	brokerList.APIVersion = "eventing.knative.dev/v1beta1"
+	brokerList.Kind = "BrokerList"
+	eventingRecorder.ListBrokers(brokerList, nil)
+	output, err := executeBrokerCommand(eventingClient, "list", "-o", "json")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(output, "\"apiVersion\": \"eventing.knative.dev/v1beta1\"", "\"items\": [],", "\"kind\": \"BrokerList\""))
+
+	eventingRecorder.Validate()
+}
+
 func TestTriggerListAllNamespace(t *testing.T) {
 	eventingClient := clienteventingv1.NewMockKnEventingClient(t)
 	eventingRecorder := eventingClient.Recorder()
diff --git a/pkg/kn/commands/channel/list_test.go b/pkg/kn/commands/channel/list_test.go
index 5e392876..cf6c0d31 100644
--- a/pkg/kn/commands/channel/list_test.go
+++ b/pkg/kn/commands/channel/list_test.go
@@ -17,6 +17,8 @@ package channel
 import (
 	"testing"
 
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"gotest.tools/v3/assert"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 
@@ -36,6 +38,29 @@ func TestChannelListNoChannelsFound(t *testing.T) {
 	cRecorder.Validate()
 }
 
+func TestChannelListNoChannelsFoundWithOutputSet(t *testing.T) {
+	cClient := clientmessagingv1.NewMockKnChannelsClient(t)
+	cRecorder := cClient.Recorder()
+	cRecorder.ListChannel(nil, nil)
+	out, err := executeChannelCommand(cClient, "list", "-o", "json")
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsAll(out, "\"apiVersion\": \"messaging.knative.dev/v1\"", "\"kind\": \"ChannelList\"", "\"items\": []"))
+	cRecorder.Validate()
+}
+
+func TestChannelListEmptyWithOutputSet(t *testing.T) {
+	cClient := clientmessagingv1.NewMockKnChannelsClient(t)
+	cRecorder := cClient.Recorder()
+	channelList := &messagingv1.ChannelList{}
+	err := util.UpdateGroupVersionKindWithScheme(channelList, messagingv1.SchemeGroupVersion, scheme.Scheme)
+	assert.NilError(t, err)
+	cRecorder.ListChannel(channelList, nil)
+	out, err := executeChannelCommand(cClient, "list", "-o", "json")
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsAll(out, "\"apiVersion\": \"messaging.knative.dev/v1\"", "\"kind\": \"ChannelList\"", "\"items\": []"))
+	cRecorder.Validate()
+}
+
 func TestChannelList(t *testing.T) {
 	cClient := clientmessagingv1.NewMockKnChannelsClient(t)
 	cRecorder := cClient.Recorder()
diff --git a/pkg/kn/commands/channel/list_types_test.go b/pkg/kn/commands/channel/list_types_test.go
index cdb80412..8f6e60ab 100644
--- a/pkg/kn/commands/channel/list_types_test.go
+++ b/pkg/kn/commands/channel/list_types_test.go
@@ -71,6 +71,15 @@ func TestChannelListTypesNoChannelInstalled(t *testing.T) {
 	assert.Check(t, util.ContainsAll(err.Error(), "no channels found on the backend, please verify the installation"))
 }
 
+func TestChannelListTypesNoChannelWithJsonOutput(t *testing.T) {
+	dynamicClient := dynamicfakeClient.CreateFakeKnDynamicClient(testNamespace)
+	assert.Equal(t, dynamicClient.Namespace(), testNamespace)
+
+	output, err := channelFakeCmd([]string{"channel", "list-types", "-o", "json"}, dynamicClient)
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsAll(strings.Join(output[:], "\n"), "\"apiVersion\": \"apiextensions.k8s.io/v1\"", "\"items\": []", "\"kind\": \"CustomResourceDefinitionList\""))
+}
+
 func TestChannelListTypesErrorDynamicClient(t *testing.T) {
 	dynamicClient := dynamicfakeClient.CreateFakeKnDynamicClient("")
 	assert.Check(t, dynamicClient.Namespace() != testNamespace)
diff --git a/pkg/kn/commands/revision/list_test.go b/pkg/kn/commands/revision/list_test.go
index 11d290f5..647baec9 100644
--- a/pkg/kn/commands/revision/list_test.go
+++ b/pkg/kn/commands/revision/list_test.go
@@ -60,6 +60,17 @@ func TestRevisionListEmpty(t *testing.T) {
 	}
 }
 
+func TestRevisionListEmptyWithJSON(t *testing.T) {
+	action, output, err := fakeRevisionList([]string{"revision", "list", "-o", "json"}, &servingv1.RevisionList{})
+	assert.NilError(t, err)
+	if action == nil {
+		t.Errorf("No action")
+	} else if !action.Matches("list", "revisions") {
+		t.Errorf("Bad action %v", action)
+	}
+	assert.Assert(t, util.ContainsAll(strings.Join(output[:], "\n"), "\"apiVersion\": \"serving.knative.dev/v1\"", "\"items\": [],", "\"kind\": \"RevisionList\""))
+}
+
 func TestRevisionListEmptyByName(t *testing.T) {
 	action, _, err := fakeRevisionList([]string{"revision", "list", "name"}, &servingv1.RevisionList{})
 	assert.NilError(t, err)
diff --git a/pkg/kn/commands/route/list_test.go b/pkg/kn/commands/route/list_test.go
index a72f0195..db8b10e8 100644
--- a/pkg/kn/commands/route/list_test.go
+++ b/pkg/kn/commands/route/list_test.go
@@ -57,6 +57,19 @@ func TestListEmpty(t *testing.T) {
 	}
 }
 
+func TestListEmptyWithJsonOutput(t *testing.T) {
+	action, output, err := fakeRouteList([]string{"route", "list", "-o", "json"}, &servingv1.RouteList{})
+	assert.NilError(t, err)
+	if action == nil {
+		t.Errorf("No action")
+	} else if !action.Matches("list", "routes") {
+		t.Errorf("Bad action %v", action)
+	}
+
+	outputJson := strings.Join(output[:], "\n")
+	assert.Assert(t, util.ContainsAll(outputJson, "\"apiVersion\": \"serving.knative.dev/v1\"", "\"items\": [],", "\"kind\": \"RouteList\""))
+}
+
 func TestRouteListDefaultOutput(t *testing.T) {
 	route1 := createMockRouteSingleTarget("foo", "foo-01234", 100)
 	route2 := createMockRouteSingleTarget("bar", "bar-98765", 100)
diff --git a/pkg/kn/commands/service/list_test.go b/pkg/kn/commands/service/list_test.go
index 18038ef6..ce2ecc0b 100644
--- a/pkg/kn/commands/service/list_test.go
+++ b/pkg/kn/commands/service/list_test.go
@@ -58,6 +58,18 @@ func TestListEmpty(t *testing.T) {
 	}
 }
 
+func TestListEmptyWithJSON(t *testing.T) {
+	action, output, err := fakeServiceList([]string{"service", "list", "-o", "json"}, &servingv1.ServiceList{})
+	assert.NilError(t, err)
+	if action == nil {
+		t.Errorf("No action")
+	} else if !action.Matches("list", "services") {
+		t.Errorf("Bad action %v", action)
+	}
+
+	assert.Assert(t, util.ContainsAll(strings.Join(output[:], "\n"), "\"apiVersion\": \"serving.knative.dev/v1\"", "\"items\": [],", "\"kind\": \"ServiceList\""))
+}
+
 func TestGetEmpty(t *testing.T) {
 	action, _, err := fakeServiceList([]string{"service", "list", "name"}, &servingv1.ServiceList{})
 	assert.NilError(t, err)
diff --git a/pkg/kn/commands/source/apiserver/list_test.go b/pkg/kn/commands/source/apiserver/list_test.go
index 22da4e6b..0a4164c2 100644
--- a/pkg/kn/commands/source/apiserver/list_test.go
+++ b/pkg/kn/commands/source/apiserver/list_test.go
@@ -17,6 +17,8 @@ package apiserver
 import (
 	"testing"
 
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"gotest.tools/v3/assert"
 
 	v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
@@ -58,3 +60,18 @@ func TestListAPIServerSourceEmpty(t *testing.T) {
 
 	apiServerRecorder.Validate()
 }
+
+func TestListAPIServerSourceEmptyWithJsonOutput(t *testing.T) {
+	apiServerClient := v1alpha22.NewMockKnAPIServerSourceClient(t)
+
+	apiServerRecorder := apiServerClient.Recorder()
+	sampleSourceList := v1alpha2.ApiServerSourceList{}
+	_ = util.UpdateGroupVersionKindWithScheme(&sampleSourceList, v1alpha2.SchemeGroupVersion, scheme.Scheme)
+	apiServerRecorder.ListAPIServerSource(&sampleSourceList, nil)
+
+	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "list", "-o", "json")
+	assert.NilError(t, err, "Sources should be listed")
+	assert.Assert(t, util.ContainsAll(out, "\"apiVersion\": \"sources.knative.dev/v1alpha2\"", "\"items\": []", "\"kind\": \"ApiServerSourceList\""))
+
+	apiServerRecorder.Validate()
+}
diff --git a/pkg/kn/commands/source/binding/list_test.go b/pkg/kn/commands/source/binding/list_test.go
index 2b2454b5..00b7663d 100644
--- a/pkg/kn/commands/source/binding/list_test.go
+++ b/pkg/kn/commands/source/binding/list_test.go
@@ -17,6 +17,8 @@ package binding
 import (
 	"testing"
 
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"gotest.tools/v3/assert"
 	v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
 
@@ -58,3 +60,18 @@ func TestListBindingEmpty(t *testing.T) {
 
 	bindingRecorder.Validate()
 }
+
+func TestListBindingEmptyWithJsonOutput(t *testing.T) {
+	bindingClient := clientv1alpha2.NewMockKnSinkBindingClient(t)
+
+	bindingRecorder := bindingClient.Recorder()
+	bindingList := v1alpha2.SinkBindingList{}
+	_ = util.UpdateGroupVersionKindWithScheme(&bindingList, v1alpha2.SchemeGroupVersion, scheme.Scheme)
+	bindingRecorder.ListSinkBindings(&bindingList, nil)
+
+	out, err := executeSinkBindingCommand(bindingClient, nil, "list", "-o", "json")
+	assert.NilError(t, err, "Sources should be listed")
+	assert.Assert(t, util.ContainsAll(out, "\"apiVersion\": \"sources.knative.dev/v1alpha2\"", "\"items\": []", "\"kind\": \"SinkBindingList\""))
+
+	bindingRecorder.Validate()
+}
diff --git a/pkg/kn/commands/source/container/list_test.go b/pkg/kn/commands/source/container/list_test.go
index 651b1807..d947431a 100644
--- a/pkg/kn/commands/source/container/list_test.go
+++ b/pkg/kn/commands/source/container/list_test.go
@@ -19,6 +19,8 @@ package container
 import (
 	"testing"
 
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"gotest.tools/v3/assert"
 	v1alpha22 "knative.dev/client/pkg/sources/v1alpha2"
 	"knative.dev/client/pkg/util"
@@ -58,3 +60,18 @@ func TestListContainerSourceEmpty(t *testing.T) {
 
 	containerRecorder.Validate()
 }
+
+func TestListContainerSourceEmptyWithJsonOutput(t *testing.T) {
+	containerClient := v1alpha22.NewMockKnContainerSourceClient(t)
+
+	containerRecorder := containerClient.Recorder()
+	sampleSourceList := v1alpha2.ContainerSourceList{}
+	_ = util.UpdateGroupVersionKindWithScheme(&sampleSourceList, v1alpha2.SchemeGroupVersion, scheme.Scheme)
+	containerRecorder.ListContainerSources(&sampleSourceList, nil)
+
+	out, err := executeContainerSourceCommand(containerClient, nil, "list", "-o", "json")
+	assert.NilError(t, err, "Sources should be listed")
+	assert.Assert(t, util.ContainsAll(out, "\"apiVersion\": \"sources.knative.dev/v1alpha2\"", "\"items\": []", "\"kind\": \"ContainerSourceList\""))
+
+	containerRecorder.Validate()
+}
diff --git a/pkg/kn/commands/source/list_test.go b/pkg/kn/commands/source/list_test.go
index 1b0291f1..26d86c84 100644
--- a/pkg/kn/commands/source/list_test.go
+++ b/pkg/kn/commands/source/list_test.go
@@ -59,6 +59,18 @@ func TestSourceListTypesNoSourcesInstalled(t *testing.T) {
 	assert.Check(t, util.ContainsAll(err.Error(), "no sources", "found", "backend", "verify", "installation"))
 }
 
+func TestSourceListTypesNoSourcesWithJsonOutput(t *testing.T) {
+	output, err := sourceFakeCmd([]string{"source", "list-types", "-o", "json"},
+		&unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": "apiextensions.k8s.io/v1",
+				"kind":       "CustomResourceDefinitionList",
+			},
+		})
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsAll(strings.Join(output[:], "\n"), "\"apiVersion\": \"apiextensions.k8s.io/v1\"", "\"items\": []", "\"kind\": \"CustomResourceDefinitionList\""))
+}
+
 func TestSourceListTypes(t *testing.T) {
 	output, err := sourceFakeCmd([]string{"source", "list-types"},
 		newSourceCRDObjWithSpec("pingsources", "sources.knative.dev", "v1alpha1", "PingSource"),
@@ -95,6 +107,15 @@ func TestSourceListNoSourcesInstalled(t *testing.T) {
 	assert.Check(t, util.ContainsAll(err.Error(), "no sources", "found", "backend", "verify", "installation"))
 }
 
+func TestSourceListEmpty(t *testing.T) {
+	output, err := sourceFakeCmd([]string{"source", "list", "-o", "json"},
+		newSourceCRDObjWithSpec("pingsources", "sources.knative.dev", "v1alpha1", "PingSource"),
+	)
+	assert.NilError(t, err)
+	outputJson := strings.Join(output[:], "\n")
+	assert.Assert(t, util.ContainsAll(outputJson, "\"apiVersion\": \"client.knative.dev/v1alpha1\"", "\"items\": [],", "\"kind\": \"SourceList\""))
+}
+
 func TestSourceList(t *testing.T) {
 	output, err := sourceFakeCmd([]string{"source", "list"},
 		newSourceCRDObjWithSpec("pingsources", "sources.knative.dev", "v1alpha1", "PingSource"),
diff --git a/pkg/kn/commands/source/ping/list_test.go b/pkg/kn/commands/source/ping/list_test.go
index 157cb04d..27bf8aeb 100644
--- a/pkg/kn/commands/source/ping/list_test.go
+++ b/pkg/kn/commands/source/ping/list_test.go
@@ -17,6 +17,8 @@ package ping
 import (
 	"testing"
 
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"gotest.tools/v3/assert"
 
 	v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
@@ -58,3 +60,18 @@ func TestListPingJobSourceEmpty(t *testing.T) {
 
 	pingRecorder.Validate()
 }
+
+func TestListPingJobSourceEmptyWithJsonOutput(t *testing.T) {
+	pingClient := clientv1alpha2.NewMockKnPingSourceClient(t)
+
+	pingRecorder := pingClient.Recorder()
+	cJSourceList := v1alpha2.PingSourceList{}
+	_ = util.UpdateGroupVersionKindWithScheme(&cJSourceList, v1alpha2.SchemeGroupVersion, scheme.Scheme)
+	pingRecorder.ListPingSource(&cJSourceList, nil)
+
+	out, err := executePingSourceCommand(pingClient, nil, "list", "-o", "json")
+	assert.NilError(t, err, "Sources should be listed")
+	assert.Assert(t, util.ContainsAll(out, "\"apiVersion\": \"sources.knative.dev/v1alpha2\"", "\"items\": []", "\"kind\": \"PingSourceList\""))
+
+	pingRecorder.Validate()
+}
diff --git a/pkg/kn/commands/subscription/list_test.go b/pkg/kn/commands/subscription/list_test.go
index f8ebd162..fb2034a1 100644
--- a/pkg/kn/commands/subscription/list_test.go
+++ b/pkg/kn/commands/subscription/list_test.go
@@ -20,6 +20,8 @@ import (
 	"strings"
 	"testing"
 
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"gotest.tools/v3/assert"
 	messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
 
@@ -37,6 +39,18 @@ func TestSubscriptionListNoSubscriptionsFound(t *testing.T) {
 	cRecorder.Validate()
 }
 
+func TestSubscriptionListNoSubscriptionsWithJsonOutput(t *testing.T) {
+	cClient := v1beta1.NewMockKnSubscriptionsClient(t)
+	cRecorder := cClient.Recorder()
+	clist := &messagingv1.SubscriptionList{}
+	_ = util.UpdateGroupVersionKindWithScheme(clist, messagingv1.SchemeGroupVersion, scheme.Scheme)
+	cRecorder.ListSubscription(clist, nil)
+	out, err := executeSubscriptionCommand(cClient, nil, "list", "-o", "json")
+	assert.NilError(t, err)
+	assert.Check(t, util.ContainsAll(out, "\"apiVersion\": \"messaging.knative.dev/v1\"", "\"items\": []", "\"kind\": \"SubscriptionList\""))
+	cRecorder.Validate()
+}
+
 func TestSubscriptionList(t *testing.T) {
 	cClient := v1beta1.NewMockKnSubscriptionsClient(t)
 	cRecorder := cClient.Recorder()
diff --git a/pkg/kn/commands/trigger/list_test.go b/pkg/kn/commands/trigger/list_test.go
index 0896e188..df33b610 100644
--- a/pkg/kn/commands/trigger/list_test.go
+++ b/pkg/kn/commands/trigger/list_test.go
@@ -18,6 +18,8 @@ import (
 	"strings"
 	"testing"
 
+	"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
+
 	"gotest.tools/v3/assert"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
@@ -69,6 +71,19 @@ func TestTriggerListEmpty(t *testing.T) {
 	eventingRecorder.Validate()
 }
 
+func TestTriggerListEmptyWithJsonOutput(t *testing.T) {
+	eventingClient := clienteventingv1.NewMockKnEventingClient(t)
+	eventingRecorder := eventingClient.Recorder()
+	triggerList := &eventingv1.TriggerList{}
+	util.UpdateGroupVersionKindWithScheme(triggerList, eventingv1.SchemeGroupVersion, scheme.Scheme)
+	eventingRecorder.ListTriggers(triggerList, nil)
+	output, err := executeTriggerCommand(eventingClient, nil, "list", "-o", "json")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(output, " \"apiVersion\": \"eventing.knative.dev/v1\"", "\"kind\": \"TriggerList\"", "\"items\": [],"))
+
+	eventingRecorder.Validate()
+}
+
 func TestTriggerListAllNamespace(t *testing.T) {
 	servingClient := clientservingv1.NewMockKnServiceClient(t)
 	servingRecorder := servingClient.Recorder()
diff --git a/test/e2e/source_list_test.go b/test/e2e/source_list_test.go
index 94473351..69bd8ad6 100644
--- a/test/e2e/source_list_test.go
+++ b/test/e2e/source_list_test.go
@@ -88,7 +88,7 @@ func TestSourceList(t *testing.T) {
 	output = sourceList(r, "--type", "testapisource0")
 	assert.Check(t, util.ContainsAll(output, "No", "sources", "found."))
 	output = sourceList(r, "--type", "TestSource", "-oyaml")
-	assert.Check(t, util.ContainsAll(output, "No", "sources", "found."))
+	assert.Check(t, util.ContainsAll(output, "apiVersion", "client.knative.dev/v1alpha1", "items", "[]", "kind", "SourceList"))
 
 	t.Log("List available source in YAML format")
 	output = sourceList(r, "--type", "PingSource,ApiServerSource", "-oyaml")