diff --git a/go.mod b/go.mod
index 699948a90c..dbc341b1c0 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
github.com/aws/aws-lambda-go v1.17.0
github.com/aws/aws-sdk-go v1.32.7
github.com/cenkalti/backoff/v4 v4.0.2
+ github.com/fatih/structtag v1.2.0
github.com/go-openapi/errors v0.19.6
github.com/go-openapi/runtime v0.19.19
github.com/go-openapi/strfmt v0.19.5
@@ -20,6 +21,7 @@ require (
github.com/kelseyhightower/envconfig v1.4.0
github.com/leodido/go-urn v1.2.0 // indirect
github.com/magefile/mage v1.9.0
+ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.6.1
github.com/tidwall/gjson v1.6.0
diff --git a/go.sum b/go.sum
index 2ee06ff517..10242c52a6 100644
--- a/go.sum
+++ b/go.sum
@@ -23,6 +23,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
+github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
diff --git a/internal/log_analysis/log_processor/pantherlog/omitempty/omitempty.go b/internal/log_analysis/log_processor/pantherlog/omitempty/omitempty.go
new file mode 100644
index 0000000000..97816050de
--- /dev/null
+++ b/internal/log_analysis/log_processor/pantherlog/omitempty/omitempty.go
@@ -0,0 +1,95 @@
+package omitempty
+
+/**
+ * Panther is a Cloud-Native SIEM for the Modern Security Team.
+ * Copyright (C) 2020 Panther Labs Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import (
+ "reflect"
+
+ "github.com/fatih/structtag"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/modern-go/reflect2"
+)
+
+// New injects omitempty option to all fields
+func New(key string) jsoniter.Extension {
+ if key == "" {
+ key = "json"
+ }
+ return &omitemptyExt{
+ key: key,
+ }
+}
+
+type omitemptyExt struct {
+ jsoniter.DummyExtension
+ key string
+}
+
+func (ext *omitemptyExt) UpdateStructDescriptor(desc *jsoniter.StructDescriptor) {
+ for _, binding := range desc.Fields {
+ field := binding.Field
+ // Assert that the struct descriptor does not contain anonymous fields (jsoniter omits them)
+ if field.Anonymous() {
+ panic("Anonymous field in struct descriptor")
+ }
+ tag := injectOmitempty(field.Tag(), ext.key)
+ binding.Field = InjectTag(field, tag)
+ }
+}
+
+func InjectTag(field reflect2.StructField, tag reflect.StructTag) reflect2.StructField {
+ return &fieldExt{
+ StructField: field,
+ tag: tag,
+ }
+}
+
+type fieldExt struct {
+ reflect2.StructField
+ tag reflect.StructTag
+}
+
+func (ext *fieldExt) Tag() reflect.StructTag {
+ if ext.tag != "" {
+ return ext.tag
+ }
+ return ext.StructField.Tag()
+}
+
+func injectOmitempty(original reflect.StructTag, key string) reflect.StructTag {
+ tags, err := structtag.Parse(string(original))
+ if err != nil {
+ return original
+ }
+ tag, err := tags.Get(key)
+ if err != nil {
+ tag := structtag.Tag{
+ Key: key,
+ Options: []string{"omitempty"},
+ }
+ _ = tags.Set(&tag)
+ return reflect.StructTag(tags.String())
+ }
+ // Assert jsoniter omits fields witg `-` name
+ if tag.Name == "-" {
+ panic("JSON-omittted field in struct descriptor")
+ }
+ tags.AddOptions(key, "omitempty")
+ return reflect.StructTag(tags.String())
+}
diff --git a/internal/log_analysis/log_processor/pantherlog/omitempty/omitempty_test.go b/internal/log_analysis/log_processor/pantherlog/omitempty/omitempty_test.go
new file mode 100644
index 0000000000..cfb5c64e02
--- /dev/null
+++ b/internal/log_analysis/log_processor/pantherlog/omitempty/omitempty_test.go
@@ -0,0 +1,70 @@
+package omitempty
+
+/**
+ * Panther is a Cloud-Native SIEM for the Modern Security Team.
+ * Copyright (C) 2020 Panther Labs Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import (
+ "testing"
+
+ jsoniter "github.com/json-iterator/go"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNew(t *testing.T) {
+ api := jsoniter.Config{}.Froze()
+ api.RegisterExtension(New("json"))
+
+ type A struct {
+ Foo string `json:"foo"`
+ Bar string `json:",omitempty"`
+ Baz string
+ Nested *A `json:"nested"`
+ }
+ type B struct {
+ Qux string `json:"qux"`
+ A
+ Ignore string `json:"-"`
+ }
+ {
+ out, err := api.MarshalToString(&A{
+ Nested: &A{},
+ })
+ require.NoError(t, err)
+ require.Equal(t, `{"nested":{}}`, out)
+ }
+ {
+ out, err := api.MarshalToString(&A{})
+ require.NoError(t, err)
+ require.Equal(t, `{}`, out)
+ }
+ {
+ out, err := api.MarshalToString(&B{})
+ require.NoError(t, err)
+ require.Equal(t, `{}`, out)
+ }
+ {
+ out, err := api.MarshalToString(&B{
+ Qux: "qux",
+ A: A{
+ Foo: "foo",
+ },
+ })
+ require.NoError(t, err)
+ require.Equal(t, `{"qux":"qux","foo":"foo"}`, out)
+ }
+}