diff --git a/README.md b/README.md index 2eee63f..96503de 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ![Marshmallow Campfire](https://raw.githubusercontent.com/PerimeterX/marshmallow/assets/campfire.png) -[![CodeQL Status](https://img.shields.io/github/workflow/status/perimeterx/marshmallow/CodeQL?label=CodeQL&logo=github)](https://github.com/PerimeterX/marshmallow/actions/workflows/codeql.yml?query=branch%3Amain++) -[![Run Tests](https://img.shields.io/github/workflow/status/perimeterx/marshmallow/Go?label=Run%20Tests&logo=github)](https://github.com/PerimeterX/marshmallow/actions/workflows/go.yml?query=branch%3Amain) -[![Dependency Review](https://img.shields.io/github/workflow/status/perimeterx/marshmallow/Dependency%20Review?label=Dependency%20Review&logo=github)](https://github.com/PerimeterX/marshmallow/actions/workflows/dependency-review.yml?query=branch%3Amain) +[![CodeQL Status](https://img.shields.io/github/actions/workflow/status/perimeterx/marshmallow/codeql.yml?branch=main&logo=github&label=CodeQL)](https://github.com/PerimeterX/marshmallow/actions/workflows/codeql.yml?query=branch%3Amain++) +[![Run Tests](https://img.shields.io/github/actions/workflow/status/perimeterx/marshmallow/go.yml?branch=main&logo=github&label=Run%20Tests)](https://github.com/PerimeterX/marshmallow/actions/workflows/go.yml?query=branch%3Amain) +[![Dependency Review](https://img.shields.io/github/actions/workflow/status/perimeterx/marshmallow/dependency-review.yml?logo=github&label=Dependency%20Review)](https://github.com/PerimeterX/marshmallow/actions/workflows/dependency-review.yml?query=branch%3Amain) [![Go Report Card](https://goreportcard.com/badge/github.com/perimeterx/marshmallow)](https://goreportcard.com/report/github.com/perimeterx/marshmallow) -![Manual Code Coverage](https://img.shields.io/badge/coverage-91.9%25-green) +![Manual Code Coverage](https://img.shields.io/badge/coverage-92.4%25-green) [![Go Reference](https://pkg.go.dev/badge/github.com/perimeterx/marshmallow.svg)](https://pkg.go.dev/github.com/perimeterx/marshmallow) [![Licence](https://img.shields.io/github/license/perimeterx/marshmallow)](LICENSE) [![Latest Release](https://img.shields.io/github/v/release/perimeterx/marshmallow)](https://github.com/PerimeterX/marshmallow/releases) @@ -49,7 +49,6 @@ import ( ) func main() { - marshmallow.EnableCache() // this is used to boost performance, read more below v := struct { Foo string `json:"foo"` Boo []int `json:"boo"` @@ -60,6 +59,8 @@ func main() { } ``` +**Examples can be found [here](example_test.go)** + ## Performance Benchmark And Alternatives Marshmallow performs best when dealing with mixed data - when some fields are known and some are unknown. @@ -183,8 +184,6 @@ Marshmallow also supports caching of refection information using and [EnableCustomCache](https://github.com/PerimeterX/marshmallow/blob/d3500aa5b0f330942b178b155da933c035dd3906/cache.go#L35). -**Examples can be found [here](example_test.go)** - # Marshmallow Logo Marshmallow logo and assets by [Adva Rom](https://www.linkedin.com/in/adva-rom-7a6738127/) are licensed under a Creative Commons Attribution 4.0 International License.
diff --git a/example_test.go b/example_test.go index bfbde6b..a35cbfa 100644 --- a/example_test.go +++ b/example_test.go @@ -3,64 +3,163 @@ package marshmallow_test import ( "fmt" "github.com/perimeterx/marshmallow" + "sync" ) -type flatStruct struct { - Foo string `json:"foo"` - Boo []int `json:"boo"` +// ExampleBasicUnmarshal shows a basic example usage of marshmallow's unmarshalling capabilities +func ExampleBasicUnmarshal() { + // we have the following data + data := []byte(`{"name":"some name", "values": [1, 2, 3], "more_data": "some stuff I am not interested in..."}`) + // we want to read the "name" field, and modify the "values" field. + // we're not directly interested in the other fields, but we do want to retain them. + + type exampleStruct struct { + Name string `json:"name"` + Values []int `json:"values"` + } + + v := exampleStruct{} + // we pass in a struct containing the fields we're interested in, + // marshmallow will populate it and return a result map containing all data + result, err := marshmallow.Unmarshal(data, &v) + if err != nil { + panic(err) + } + + fmt.Printf("Struct data: %+v\n", v) + fmt.Printf("Map data: %+v\n", result) + + // now we can work with the struct data in a safe and maintainable manner + v.Values[0] = 42 + fmt.Printf("Name is %s\n", v.Name) + + // pointer value changes in the struct will also be visible from the map + // more info at https://github.com/PerimeterX/marshmallow/issues/23#issuecomment-1403409592 + fmt.Printf("Map data after changes: %+v\n", result) + + // Output: + // Struct data: {Name:some name Values:[1 2 3]} + // Map data: map[more_data:some stuff I am not interested in... name:some name values:[1 2 3]] + // Name is some name + // Map data after changes: map[more_data:some stuff I am not interested in... name:some name values:[42 2 3]] +} + +// ExampleExcludeKnownFields shows how to use the WithExcludeKnownFieldsFromMap option to exclude struct fields +// from the result map. more info at https://github.com/PerimeterX/marshmallow/issues/16 +func ExampleExcludeKnownFields() { + type exampleStruct struct { + Foo string `json:"foo"` + Boo []int `json:"boo"` + } + + // unmarshal with mode marshmallow.ModeExcludeKnownFieldsFromReturnedMap + // this will return unmarshalled result without known fields + v := exampleStruct{} + result, err := marshmallow.Unmarshal( + []byte(`{"foo":"bar","boo":[1,2,3],"goo":"untyped"}`), + &v, + marshmallow.WithExcludeKnownFieldsFromMap(true), + ) + fmt.Printf("v=%+v, result=%+v, err=%T\n", v, result, err) + // Output: + // v={Foo:bar Boo:[1 2 3]}, result=map[goo:untyped], err= +} + +// ExampleJSONDataHandler shows how to capture nested unknown fields +// more info at https://github.com/PerimeterX/marshmallow/issues/15 +func ExampleJSONDataHandler() { + type parentStruct struct { + Known string `json:"known"` + Nested childStruct `json:"nested"` + } + + data := []byte(`{"known": "foo","unknown": "boo","nested": {"known": "goo","unknown": "doo"}}`) + p := &parentStruct{} + _, err := marshmallow.Unmarshal(data, p) + fmt.Printf("err: %v\n", err) + fmt.Printf("nested data: %+v\n", p.Nested.Data) + // Output: + // err: + // nested data: map[known:goo unknown:doo] } -func ExampleUnmarshal() { - // enable marshmallow cache to boost up performance by reusing field type information. +// ExampleCache shows how to enable marshmallow cache to boost up performance by reusing field type information. +// more info: https://github.com/PerimeterX/marshmallow/blob/22e3c7fe4423d7c5f317d95f84de524253e0aed3/cache.go#L35 +func ExampleCache() { + // enable default cache marshmallow.EnableCache() + type exampleStruct struct { + Foo string `json:"foo"` + Boo []int `json:"boo"` + } + v := exampleStruct{} + _, _ = marshmallow.Unmarshal([]byte(`{"foo":"bar","boo":[1,2,3]}`), &v) + + // enable custom cache, you can pass any implementation of the marshmallow.Cache interface + // this lets you control the size of the cache, eviction policy, or any other aspect of it. + marshmallow.EnableCustomCache(&sync.Map{}) +} + +// ExampleUnmarshalErrorHandling shows all error handling capabilities of marshmallow.Unmarshal +func ExampleUnmarshalErrorHandling() { + type exampleStruct struct { + Foo string `json:"foo"` + Boo []int `json:"boo"` + } + // unmarshal with mode marshmallow.ModeFailOnFirstError and valid value // this will finish unmarshalling and return a nil err - v := flatStruct{} + v := exampleStruct{} result, err := marshmallow.Unmarshal([]byte(`{"foo":"bar","boo":[1,2,3]}`), &v) fmt.Printf("ModeFailOnFirstError and valid value: v=%+v, result=%+v, err=%T\n", v, result, err) // unmarshal with mode marshmallow.ModeFailOnFirstError and invalid value // this will return nil result and an error - v = flatStruct{} + v = exampleStruct{} result, err = marshmallow.Unmarshal([]byte(`{"foo":2,"boo":[1,2,3]}`), &v) fmt.Printf("ModeFailOnFirstError and invalid value: result=%+v, err=%T\n", result, err) // unmarshal with mode marshmallow.ModeAllowMultipleErrors and valid value // this will finish unmarshalling and return a nil err - v = flatStruct{} - result, err = marshmallow.Unmarshal([]byte(`{"foo":"bar","boo":[1,2,3]}`), &v, - marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors)) + v = exampleStruct{} + result, err = marshmallow.Unmarshal( + []byte(`{"foo":"bar","boo":[1,2,3]}`), + &v, + marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors), + ) fmt.Printf("ModeAllowMultipleErrors and valid value: v=%+v, result=%+v, err=%T\n", v, result, err) // unmarshal with mode marshmallow.ModeAllowMultipleErrors and invalid value // this will return a partially populated result and an error - v = flatStruct{} - result, err = marshmallow.Unmarshal([]byte(`{"foo":2,"boo":[1,2,3]}`), &v, - marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors)) + v = exampleStruct{} + result, err = marshmallow.Unmarshal( + []byte(`{"foo":2,"boo":[1,2,3]}`), + &v, + marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors), + ) fmt.Printf("ModeAllowMultipleErrors and invalid value: result=%+v, err=%T\n", result, err) // unmarshal with mode marshmallow.ModeFailOverToOriginalValue and valid value // this will finish unmarshalling and return a nil err - v = flatStruct{} - result, err = marshmallow.Unmarshal([]byte(`{"foo":"bar","boo":[1,2,3]}`), &v, - marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue)) + v = exampleStruct{} + result, err = marshmallow.Unmarshal( + []byte(`{"foo":"bar","boo":[1,2,3]}`), + &v, + marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue), + ) fmt.Printf("ModeFailOverToOriginalValue and valid value: v=%+v, result=%+v, err=%T\n", v, result, err) // unmarshal with mode marshmallow.ModeFailOverToOriginalValue and invalid value // this will return a fully unmarshalled result, failing to the original invalid values, and an error - v = flatStruct{} - result, err = marshmallow.Unmarshal([]byte(`{"foo":2,"boo":[1,2,3]}`), &v, - marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue)) + v = exampleStruct{} + result, err = marshmallow.Unmarshal( + []byte(`{"foo":2,"boo":[1,2,3]}`), + &v, + marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue), + ) fmt.Printf("ModeFailOverToOriginalValue and invalid value: result=%+v, err=%T\n", result, err) - // unmarshal with mode marshmallow.ModeExcludeKnownFieldsFromReturnedMap and valid value - // this will return unmarshalled result without known fields - v = flatStruct{} - result, err = marshmallow.Unmarshal([]byte(`{"foo":"bar","boo":[1,2,3],"goo":"untyped"}`), &v, - marshmallow.WithExcludeKnownFieldsFromMap(true)) - fmt.Printf("ModeExcludeKnownFieldsFromReturnedMap and valid value: v=%+v, result=%+v, err=%T\n", v, result, err) - // Output: // ModeFailOnFirstError and valid value: v={Foo:bar Boo:[1 2 3]}, result=map[boo:[1 2 3] foo:bar], err= // ModeFailOnFirstError and invalid value: result=map[], err=*jlexer.LexerError @@ -68,57 +167,71 @@ func ExampleUnmarshal() { // ModeAllowMultipleErrors and invalid value: result=map[boo:[1 2 3]], err=*marshmallow.MultipleLexerError // ModeFailOverToOriginalValue and valid value: v={Foo:bar Boo:[1 2 3]}, result=map[boo:[1 2 3] foo:bar], err= // ModeFailOverToOriginalValue and invalid value: result=map[boo:[1 2 3] foo:2], err=*marshmallow.MultipleLexerError - // ModeExcludeKnownFieldsFromReturnedMap and valid value: v={Foo:bar Boo:[1 2 3]}, result=map[goo:untyped], err= } -func ExampleUnmarshalFromJSONMap() { - // enable marshmallow cache to boost up performance by reusing field type information. - marshmallow.EnableCache() +// ExampleUnmarshalFromJSONMap shows all error handling capabilities of marshmallow.UnmarshalFromJSONMap +func ExampleUnmarshalFromJSONMapErrorHandling() { + type exampleStruct struct { + Foo string `json:"foo"` + Boo []int `json:"boo"` + } // unmarshal with mode marshmallow.ModeFailOnFirstError and valid value // this will finish unmarshalling and return a nil err - v := flatStruct{} - result, err := marshmallow.UnmarshalFromJSONMap( - map[string]interface{}{"foo": "bar", "boo": []interface{}{float64(1), float64(2), float64(3)}}, &v) + v := exampleStruct{} + data := map[string]interface{}{"foo": "bar", "boo": []interface{}{float64(1), float64(2), float64(3)}} + result, err := marshmallow.UnmarshalFromJSONMap(data, &v) fmt.Printf("ModeFailOnFirstError and valid value: v=%+v, result=%+v, err=%T\n", v, result, err) // unmarshal with mode marshmallow.ModeFailOnFirstError and invalid value // this will return nil result and an error - v = flatStruct{} - result, err = marshmallow.UnmarshalFromJSONMap( - map[string]interface{}{"foo": float64(2), "boo": []interface{}{float64(1), float64(2), float64(3)}}, &v) + v = exampleStruct{} + data = map[string]interface{}{"foo": float64(2), "boo": []interface{}{float64(1), float64(2), float64(3)}} + result, err = marshmallow.UnmarshalFromJSONMap(data, &v) fmt.Printf("ModeFailOnFirstError and invalid value: result=%+v, err=%T\n", result, err) // unmarshal with mode marshmallow.ModeAllowMultipleErrors and valid value // this will finish unmarshalling and return a nil err - v = flatStruct{} + v = exampleStruct{} + data = map[string]interface{}{"foo": "bar", "boo": []interface{}{float64(1), float64(2), float64(3)}} result, err = marshmallow.UnmarshalFromJSONMap( - map[string]interface{}{"foo": "bar", "boo": []interface{}{float64(1), float64(2), float64(3)}}, &v, - marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors)) + data, + &v, + marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors), + ) fmt.Printf("ModeAllowMultipleErrors and valid value: v=%+v, result=%+v, err=%T\n", v, result, err) // unmarshal with mode marshmallow.ModeAllowMultipleErrors and invalid value // this will return a partially populated result and an error - v = flatStruct{} + v = exampleStruct{} + data = map[string]interface{}{"foo": float64(2), "boo": []interface{}{float64(1), float64(2), float64(3)}} result, err = marshmallow.UnmarshalFromJSONMap( - map[string]interface{}{"foo": float64(2), "boo": []interface{}{float64(1), float64(2), float64(3)}}, &v, - marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors)) + data, + &v, + marshmallow.WithMode(marshmallow.ModeAllowMultipleErrors), + ) fmt.Printf("ModeAllowMultipleErrors and invalid value: result=%+v, err=%T\n", result, err) // unmarshal with mode marshmallow.ModeFailOverToOriginalValue and valid value // this will finish unmarshalling and return a nil err - v = flatStruct{} + v = exampleStruct{} + data = map[string]interface{}{"foo": "bar", "boo": []interface{}{float64(1), float64(2), float64(3)}} result, err = marshmallow.UnmarshalFromJSONMap( - map[string]interface{}{"foo": "bar", "boo": []interface{}{float64(1), float64(2), float64(3)}}, &v, - marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue)) + data, + &v, + marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue), + ) fmt.Printf("ModeFailOverToOriginalValue and valid value: v=%+v, result=%+v, err=%T\n", v, result, err) // unmarshal with mode marshmallow.ModeFailOverToOriginalValue and invalid value // this will return a fully unmarshalled result, failing to the original invalid values, and an error - v = flatStruct{} + v = exampleStruct{} + data = map[string]interface{}{"foo": float64(2), "boo": []interface{}{float64(1), float64(2), float64(3)}} result, err = marshmallow.UnmarshalFromJSONMap( - map[string]interface{}{"foo": float64(2), "boo": []interface{}{float64(1), float64(2), float64(3)}}, &v, - marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue)) + data, + &v, + marshmallow.WithMode(marshmallow.ModeFailOverToOriginalValue), + ) fmt.Printf("ModeFailOverToOriginalValue and invalid value: result=%+v, err=%T\n", result, err) // Output: // ModeFailOnFirstError and valid value: v={Foo:bar Boo:[1 2 3]}, result=map[boo:[1 2 3] foo:bar], err= @@ -129,11 +242,6 @@ func ExampleUnmarshalFromJSONMap() { // ModeFailOverToOriginalValue and invalid value: result=map[boo:[1 2 3] foo:2], err=*marshmallow.MultipleError } -type parentStruct struct { - Known string `json:"known"` - Nested childStruct `json:"nested"` -} - type childStruct struct { Known string `json:"known"` @@ -143,14 +251,3 @@ type childStruct struct { func (c *childStruct) HandleJSONData(data map[string]interface{}) { c.Data = data } - -func ExampleJSONDataHandler() { - data := []byte(`{"known": "foo","unknown": "boo","nested": {"known": "goo","unknown": "doo"}}`) - p := &parentStruct{} - _, err := marshmallow.Unmarshal(data, p) - fmt.Printf("err: %v\n", err) - fmt.Printf("nested data: %+v\n", p.Nested.Data) - // Output: - // err: - // nested data: map[known:goo unknown:doo] -}