Skip to content

Commit

Permalink
Merge pull request #231 from mgilbir/json-xml-nil-options
Browse files Browse the repository at this point in the history
JSON/XML/CSV nil options
  • Loading branch information
brianvoe committed Mar 28, 2023
2 parents bf750ef + 4dc8e21 commit af9e266
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 15 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,10 @@ All functions also exist as methods on the Faker struct

### File

Passing `nil` to `CSV`, `JSON` or `XML` it will auto generate data using a random set of generators.

```go
CSV(co *CSVOptions) ([]byte, error)
JSON(jo *JSONOptions) ([]byte, error)
XML(xo *XMLOptions) ([]byte, error)
FileExtension() string
Expand Down
16 changes: 13 additions & 3 deletions csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,28 @@ import (

// CSVOptions defines values needed for csv generation
type CSVOptions struct {
Delimiter string `json:"delimiter" xml:"delimiter"`
RowCount int `json:"row_count" xml:"row_count"`
Fields []Field `json:"fields" xml:"fields"`
Delimiter string `json:"delimiter" xml:"delimiter" fake:"{randomstring:[,,tab]}"`
RowCount int `json:"row_count" xml:"row_count" fake:"{number:1,10}"`
Fields []Field `json:"fields" xml:"fields" fake:"{internal_exampleFields}"`
}

// CSV generates an object or an array of objects in json format
// A nil CSVOptions returns a randomly structured CSV.
func CSV(co *CSVOptions) ([]byte, error) { return csvFunc(globalFaker.Rand, co) }

// CSV generates an object or an array of objects in json format
// A nil CSVOptions returns a randomly structured CSV.
func (f *Faker) CSV(co *CSVOptions) ([]byte, error) { return csvFunc(f.Rand, co) }

func csvFunc(r *rand.Rand, co *CSVOptions) ([]byte, error) {
if co == nil {
// We didn't get a CSVOptions, so create a new random one
err := Struct(&co)
if err != nil {
return nil, err
}
}

// Check delimiter
if co.Delimiter == "" {
co.Delimiter = ","
Expand Down
10 changes: 10 additions & 0 deletions csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ func TestCSVLookup(t *testing.T) {
// t.Fatal(fmt.Sprintf("%s", value.([]byte)))
}

func TestCSVNoOptions(t *testing.T) {
Seed(11)

// if CSVOptions is nil -> get a random CSVOptions
_, err := CSV(nil)
if err != nil {
t.Fatal(err.Error())
}
}

func BenchmarkCSVLookup100(b *testing.B) {
faker := New(0)

Expand Down
20 changes: 15 additions & 5 deletions json.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (

// JSONOptions defines values needed for json generation
type JSONOptions struct {
Type string `json:"type" xml:"type"` // array or object
RowCount int `json:"row_count" xml:"row_count"`
Fields []Field `json:"fields" xml:"fields"`
Type string `json:"type" xml:"type" fake:"{randomstring:[array,object]}"` // array or object
RowCount int `json:"row_count" xml:"row_count" fake:"{number:1,10}"`
Fields []Field `json:"fields" xml:"fields" fake:"{internal_exampleFields}"`
Indent bool `json:"indent" xml:"indent"`
}

Expand Down Expand Up @@ -54,14 +54,24 @@ func (okv jsonOrderedKeyVal) MarshalJSON() ([]byte, error) {
return buf.Bytes(), nil
}

// JSON generates an object or an array of objects in json format
// JSON generates an object or an array of objects in json format.
// A nil JSONOptions returns a randomly structured JSON.
func JSON(jo *JSONOptions) ([]byte, error) { return jsonFunc(globalFaker.Rand, jo) }

// JSON generates an object or an array of objects in json format
// JSON generates an object or an array of objects in json format.
// A nil JSONOptions returns a randomly structured JSON.
func (f *Faker) JSON(jo *JSONOptions) ([]byte, error) { return jsonFunc(f.Rand, jo) }

// JSON generates an object or an array of objects in json format
func jsonFunc(r *rand.Rand, jo *JSONOptions) ([]byte, error) {
if jo == nil {
// We didn't get a JSONOptions, so create a new random one
err := Struct(&jo)
if err != nil {
return nil, err
}
}

// Check to make sure they passed in a type
if jo.Type != "array" && jo.Type != "object" {
return nil, errors.New("invalid type, must be array or object")
Expand Down
10 changes: 10 additions & 0 deletions json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,16 @@ func TestJSONNoCount(t *testing.T) {
}
}

func TestJSONNoOptions(t *testing.T) {
Seed(11)

// if JSONOptions is nil -> get a random JSONOptions
_, err := JSON(nil)
if err != nil {
t.Fatal(err.Error())
}
}

func BenchmarkJSONLookup100(b *testing.B) {
faker := New(0)

Expand Down
53 changes: 49 additions & 4 deletions lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,35 @@ import (
var FuncLookups map[string]Info
var lockFuncLookups sync.Mutex

// internalFuncLookups is the internal map array with mapping to all available data
var internalFuncLookups map[string]Info = map[string]Info{
"internal_exampleFields": {
Description: "Example fields for generating xml and json",
Example: `{"name":"{firstname}","age":"{number:1,100}"}`,
Output: "gofakeit.Field",
Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
name, _ := getRandomFuncLookup(r, true)
return Field{
Name: name,
Function: name,
}, nil
},
},
}

func getRandomFuncLookup(r *rand.Rand, excludeWithParams bool) (string, Info) {
var keys []string
for k, v := range FuncLookups {
if excludeWithParams && len(v.Params) != 0 {
continue
}
keys = append(keys, k)
}

selected := keys[r.Intn(len(keys))]
return selected, FuncLookups[selected]
}

// MapParams is the values to pass into a lookup generate
type MapParams map[string]MapParamsValue

Expand Down Expand Up @@ -170,6 +199,10 @@ func (m *MapParamsValue) UnmarshalJSON(data []byte) error {

// AddFuncLookup takes a field and adds it to map
func AddFuncLookup(functionName string, info Info) {
if _, ok := internalFuncLookups[functionName]; ok {
panic(fmt.Sprintf("Function %s is used internally and cannot be overwritten", functionName))
}

if FuncLookups == nil {
FuncLookups = make(map[string]Info)
}
Expand All @@ -186,16 +219,28 @@ func AddFuncLookup(functionName string, info Info) {

// GetFuncLookup will lookup
func GetFuncLookup(functionName string) *Info {
info, ok := FuncLookups[functionName]
if !ok {
return nil
var info Info
var ok bool

info, ok = internalFuncLookups[functionName]
if ok {
return &info
}

info, ok = FuncLookups[functionName]
if ok {
return &info
}

return &info
return nil
}

// RemoveFuncLookup will remove a function from lookup
func RemoveFuncLookup(functionName string) {
if _, ok := internalFuncLookups[functionName]; ok {
panic(fmt.Sprintf("Function %s is used internally and cannot be overwritten", functionName))
}

_, ok := FuncLookups[functionName]
if !ok {
return
Expand Down
16 changes: 13 additions & 3 deletions xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (

// XMLOptions defines values needed for json generation
type XMLOptions struct {
Type string `json:"type" xml:"type"` // single or multiple
Type string `json:"type" xml:"type" fake:"{randomstring:[array,single]}"` // single or array
RootElement string `json:"root_element" xml:"root_element"`
RecordElement string `json:"record_element" xml:"record_element"`
RowCount int `json:"row_count" xml:"row_count"`
Fields []Field `json:"fields" xml:"fields"`
RowCount int `json:"row_count" xml:"row_count" fake:"{number:1,10}"`
Fields []Field `json:"fields" xml:"fields" fake:"{internal_exampleFields}"`
Indent bool `json:"indent" xml:"indent"`
}

Expand Down Expand Up @@ -128,12 +128,22 @@ func xmlMapLoop(e *xml.Encoder, m *xmlMap) error {
}

// XML generates an object or an array of objects in json format
// A nil XMLOptions returns a randomly structured XML.
func XML(xo *XMLOptions) ([]byte, error) { return xmlFunc(globalFaker.Rand, xo) }

// XML generates an object or an array of objects in json format
// A nil XMLOptions returns a randomly structured XML.
func (f *Faker) XML(xo *XMLOptions) ([]byte, error) { return xmlFunc(f.Rand, xo) }

func xmlFunc(r *rand.Rand, xo *XMLOptions) ([]byte, error) {
if xo == nil {
// We didn't get a XMLOptions, so create a new random one
err := Struct(&xo)
if err != nil {
return nil, err
}
}

// Check to make sure they passed in a type
if xo.Type != "single" && xo.Type != "array" {
return nil, errors.New("invalid type, must be array or object")
Expand Down
10 changes: 10 additions & 0 deletions xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,16 @@ func TestXMLLookup(t *testing.T) {
}
}

func TestXMLNoOptions(t *testing.T) {
Seed(11)

// if XMLOptions is nil -> get a random XMLOptions
_, err := XML(nil)
if err != nil {
t.Fatal(err.Error())
}
}

func BenchmarkXMLLookup100(b *testing.B) {
faker := New(0)

Expand Down

0 comments on commit af9e266

Please sign in to comment.