Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON/XML/CSV nil options #231

Merged
merged 12 commits into from Mar 28, 2023
3 changes: 3 additions & 0 deletions README.md
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
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
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
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
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
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
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
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