Skip to content

Commit

Permalink
Add jsoniter extension to force omitempty on all fields (#1204)
Browse files Browse the repository at this point in the history
* Add jsoniter extension to force omitempty on all fields
  • Loading branch information
alxarch authored Jul 24, 2020
1 parent 10e6ebe commit d4481f0
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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())
}
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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)
}
}

0 comments on commit d4481f0

Please sign in to comment.