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

Safe json.RawMessage and json.Number #233

Merged
merged 9 commits into from Apr 10, 2023
13 changes: 7 additions & 6 deletions csv.go
Expand Up @@ -20,16 +20,16 @@ type CSVOptions struct {

// 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) }
func CSV(co *CSVOptions) ([]byte, error) { return csvFunc(globalFaker, 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 (f *Faker) CSV(co *CSVOptions) ([]byte, error) { return csvFunc(f, co) }

func csvFunc(r *rand.Rand, co *CSVOptions) ([]byte, error) {
func csvFunc(f *Faker, co *CSVOptions) ([]byte, error) {
if co == nil {
// We didn't get a CSVOptions, so create a new random one
err := Struct(&co)
err := f.Struct(&co)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -84,7 +84,7 @@ func csvFunc(r *rand.Rand, co *CSVOptions) ([]byte, error) {
return nil, errors.New("invalid function, " + field.Function + " does not exist")
}

value, err := funcInfo.Generate(r, &field.Params, funcInfo)
value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -175,7 +175,8 @@ func addFileCSVLookup() {
}
co.Delimiter = delimiter

csvOut, err := csvFunc(r, &co)
f := &Faker{Rand: r}
csvOut, err := csvFunc(f, &co)
if err != nil {
return nil, err
}
Expand Down
99 changes: 92 additions & 7 deletions json.go
Expand Up @@ -4,7 +4,10 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math/rand"
"reflect"
"strconv"
)

// JSONOptions defines values needed for json generation
Expand Down Expand Up @@ -56,17 +59,17 @@ func (okv jsonOrderedKeyVal) MarshalJSON() ([]byte, error) {

// 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) }
func JSON(jo *JSONOptions) ([]byte, error) { return jsonFunc(globalFaker, jo) }

// 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) }
func (f *Faker) JSON(jo *JSONOptions) ([]byte, error) { return jsonFunc(f, jo) }

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

// Call function value
value, err := funcInfo.Generate(r, &field.Params, funcInfo)
value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -153,7 +156,7 @@ func jsonFunc(r *rand.Rand, jo *JSONOptions) ([]byte, error) {
}

// Call function value
value, err := funcInfo.Generate(r, &field.Params, funcInfo)
value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -244,7 +247,89 @@ func addFileJSONLookup() {
}
jo.Indent = indent

return jsonFunc(r, &jo)
f := &Faker{Rand: r}
return jsonFunc(f, &jo)
},
})
}

// encoding/json.RawMessage is a special case of []byte
// it cannot be handled as a reflect.Array/reflect.Slice
// because it needs additional structure in the output
func rJsonRawMessage(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error {
b, err := f.JSON(nil)
if err != nil {
return err
}

v.SetBytes(b)
return nil
}

// encoding/json.Number is a special case of string
// that represents a JSON number literal.
// It cannot be handled as a string because it needs to
// represent an integer or a floating-point number.
func rJsonNumber(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error {
var ret json.Number

var numberType string

if tag == "" {
numberType = f.RandomString([]string{"int", "float"})

switch numberType {
case "int":
retInt := f.Int16()
ret = json.Number(strconv.Itoa(int(retInt)))
case "float":
retFloat := f.Float64()
ret = json.Number(strconv.FormatFloat(retFloat, 'f', -1, 64))
}
} else {
fName, fParams := parseNameAndParamsFromTag(tag)
info := GetFuncLookup(fName)
if info == nil {
return fmt.Errorf("invalid function, %s does not exist", fName)
}

// Parse map params
mapParams := parseMapParams(info, fParams)

valueIface, err := info.Generate(f.Rand, mapParams, info)
if err != nil {
return err
}

switch value := valueIface.(type) {
case int:
ret = json.Number(strconv.FormatInt(int64(value), 10))
case int8:
ret = json.Number(strconv.FormatInt(int64(value), 10))
case int16:
ret = json.Number(strconv.FormatInt(int64(value), 10))
case int32:
ret = json.Number(strconv.FormatInt(int64(value), 10))
case int64:
ret = json.Number(strconv.FormatInt(int64(value), 10))
case uint:
ret = json.Number(strconv.FormatUint(uint64(value), 10))
case uint8:
ret = json.Number(strconv.FormatUint(uint64(value), 10))
case uint16:
ret = json.Number(strconv.FormatUint(uint64(value), 10))
case uint32:
ret = json.Number(strconv.FormatUint(uint64(value), 10))
case uint64:
ret = json.Number(strconv.FormatUint(uint64(value), 10))
case float32:
ret = json.Number(strconv.FormatFloat(float64(value), 'f', -1, 64))
case float64:
ret = json.Number(strconv.FormatFloat(float64(value), 'f', -1, 64))
default:
return fmt.Errorf("invalid type, %s is not a valid type for json.Number", reflect.TypeOf(value))
}
}
v.Set(reflect.ValueOf(ret))
return nil
}
118 changes: 118 additions & 0 deletions json_test.go
Expand Up @@ -362,6 +362,124 @@ func TestJSONNoOptions(t *testing.T) {
}
}

func TestJSONRawMessage(t *testing.T) {
type J struct {
Field json.RawMessage `json:"field"`
}

Seed(100)

var objs []J
Slice(&objs)

_, err := json.Marshal(objs)
if err != nil {
t.Fatal(err)
}
}

func TestJSONRawMessageWithTag(t *testing.T) {
type J struct {
Field json.RawMessage `json:"field" faker:"json"`
}

Seed(100)

var objs []J
Slice(&objs)

_, err := json.Marshal(objs)
if err != nil {
t.Fatal(err)
}
}

func TestJSONNumber(t *testing.T) {
type J struct {
Field json.Number `json:"field"`
}

Seed(100)

var objs []J
Slice(&objs)

_, err := json.Marshal(objs)
if err != nil {
t.Fatal(err)
}
}

func TestJSONNumberWithTag(t *testing.T) {
type J struct {
Field json.Number `json:"field" fake:"number:3,7"`
}

Seed(100)

var objs []J
Slice(&objs)

got, err := objs[0].Field.Int64()
if err != nil {
t.Fatal(err)
}
if got < 3 || got > 7 {
t.Errorf("Expected a number between 3 and 7, got %d", got)
}

_, err = json.Marshal(objs)
if err != nil {
t.Fatal(err)
}
}

func ExampleJSONNumberWithTag() {
Seed(10)

type J struct {
FieldNumber json.Number `fake:"number:3,7"`
FieldInt8 json.Number `fake:"int8"`
FieldInt16 json.Number `fake:"int16"`
FieldInt32 json.Number `fake:"int32"`
FieldInt64 json.Number `fake:"int64"`
FieldUint8 json.Number `fake:"uint8"`
FieldUint16 json.Number `fake:"uint16"`
FieldUint32 json.Number `fake:"uint32"`
FieldUint64 json.Number `fake:"uint64"`
FieldFloat32 json.Number `fake:"float32"`
FieldFloat64 json.Number `fake:"float64range:12,72"`
}

var obj J
Struct(&obj)

fmt.Printf("obj.FieldNumber = %+v\n", obj.FieldNumber)
fmt.Printf("obj.FieldInt8 = %+v\n", obj.FieldInt8)
fmt.Printf("obj.FieldInt16 = %+v\n", obj.FieldInt16)
fmt.Printf("obj.FieldInt32 = %+v\n", obj.FieldInt32)
fmt.Printf("obj.FieldInt64 = %+v\n", obj.FieldInt64)
fmt.Printf("obj.FieldUint8 = %+v\n", obj.FieldUint8)
fmt.Printf("obj.FieldUint16 = %+v\n", obj.FieldUint16)
fmt.Printf("obj.FieldUint32 = %+v\n", obj.FieldUint32)
fmt.Printf("obj.FieldUint64 = %+v\n", obj.FieldUint64)
fmt.Printf("obj.FieldFloat32 = %+v\n", obj.FieldFloat32)
fmt.Printf("obj.FieldFloat64 = %+v\n", obj.FieldFloat64)

// Output:
// obj.FieldNumber = 3
// obj.FieldInt8 = 16
// obj.FieldInt16 = 10619
// obj.FieldInt32 = -1654523813
// obj.FieldInt64 = -4710905755560118665
// obj.FieldUint8 = 200
// obj.FieldUint16 = 28555
// obj.FieldUint32 = 162876094
// obj.FieldUint64 = 7956601014869229133
// obj.FieldFloat32 = 9227009415507442000000000000000000000
// obj.FieldFloat64 = 62.323882731848215
}

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

Expand Down
40 changes: 33 additions & 7 deletions lookup.go
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math/rand"
"reflect"
"sort"
"strconv"
"strings"
"sync"
Expand All @@ -17,11 +18,11 @@ 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}"}`,
Description: "Example fields for generating csv, json and xml",
Output: "gofakeit.Field",
Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
name, _ := getRandomFuncLookup(r, true)
name, _ := getRandomFuncLookup(r, excludeWithParams,
validTypes("string", "int", "[]string", "[]int"))
return Field{
Name: name,
Function: name,
Expand All @@ -30,15 +31,40 @@ var internalFuncLookups map[string]Info = map[string]Info{
},
}

func getRandomFuncLookup(r *rand.Rand, excludeWithParams bool) (string, Info) {
// filterFuncLookup returns true when the lookup should be accepted
type filterFuncLookup func(Info) bool

var (
excludeWithParams filterFuncLookup = func(info Info) bool {
return len(info.Params) == 0
}

validTypes = func(acceptedTypes ...string) filterFuncLookup {
return func(info Info) bool {
for _, t := range acceptedTypes {
if info.Output == t {
return true
}
}
return false
}
}
)

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

sort.Stable(sort.StringSlice(keys))

selected := keys[r.Intn(len(keys))]
return selected, FuncLookups[selected]
}
Expand Down
18 changes: 18 additions & 0 deletions struct.go
Expand Up @@ -29,6 +29,24 @@ func structFunc(f *Faker, v interface{}) error {
}

func r(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error {
// Handle special types

if t.PkgPath() == "encoding/json" {
// encoding/json has two special types:
// - RawMessage
// - Number

switch t.Name() {
case "RawMessage":
return rJsonRawMessage(f, t, v, tag, size)
case "Number":
return rJsonNumber(f, t, v, tag, size)
default:
return errors.New("unknown encoding/json type: " + t.Name())
}
}

// Handle generic types
switch t.Kind() {
case reflect.Ptr:
return rPointer(f, t, v, tag, size)
Expand Down