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
Prev Previous commit
Move to json.go and finish implementation for json.Number
  • Loading branch information
mgilbir committed Apr 4, 2023
commit 3eccca0f59d96d96b064b488cc8ba7b630d81b69
84 changes: 84 additions & 0 deletions json.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,10 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math/rand"
"reflect"
"strconv"
)

// JSONOptions defines values needed for json generation
@@ -249,3 +252,84 @@ func addFileJSONLookup() {
},
})
}

// 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
}
46 changes: 46 additions & 0 deletions json_test.go
Original file line number Diff line number Diff line change
@@ -434,6 +434,52 @@ func TestJSONNumberWithTag(t *testing.T) {
}
}

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)

36 changes: 1 addition & 35 deletions struct.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package gofakeit

import (
"encoding/json"
"errors"
"reflect"
"strconv"
@@ -31,6 +30,7 @@ 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
@@ -71,40 +71,6 @@ func r(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error {
return nil
}

// 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

numberType := f.RandomInt([]int{0, 1})
switch numberType {
case 0:
retInt := f.Int16()
ret = json.Number(strconv.Itoa(int(retInt)))
case 1:
retFloat := f.Float64()
ret = json.Number(strconv.FormatFloat(retFloat, 'f', -1, 64))
}

v.Set(reflect.ValueOf(ret))
return nil
}

func rCustom(f *Faker, t reflect.Type, v reflect.Value, tag string) error {
// If tag is empty return error
if tag == "" {