package gofakeit import ( "bytes" "encoding/json" "errors" "fmt" "math/rand" "reflect" "strconv" ) // JSONOptions defines values needed for json generation type JSONOptions struct { 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}"` Indent bool `json:"indent" xml:"indent"` Fields []Field `json:"fields" xml:"fields" fake:"{fields}"` } type jsonKeyVal struct { Key string Value any } type jsonOrderedKeyVal []*jsonKeyVal func (okv jsonOrderedKeyVal) MarshalJSON() ([]byte, error) { var buf bytes.Buffer buf.WriteString("{") for i, kv := range okv { // Add comma to all except last one if i != 0 { buf.WriteString(",") } // Marshal key and write key, err := json.Marshal(kv.Key) if err != nil { return nil, err } buf.Write(key) // Write colon separator buf.WriteString(":") // Marshal value and write val, err := json.Marshal(kv.Value) if err != nil { return nil, err } buf.Write(val) } buf.WriteString("}") return buf.Bytes(), nil } // 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, 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, jo) } // JSON generates an object or an array of objects in json format func jsonFunc(f *Faker, jo *JSONOptions) ([]byte, error) { if jo == nil { // We didn't get a JSONOptions, so create a new random one err := f.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") } if jo.Fields == nil || len(jo.Fields) <= 0 { return nil, errors.New("must pass fields in order to build json object(s)") } if jo.Type == "object" { v := make(jsonOrderedKeyVal, len(jo.Fields)) // Loop through fields and add to them to map[string]any for i, field := range jo.Fields { if field.Function == "autoincrement" { // Object only has one v[i] = &jsonKeyVal{Key: field.Name, Value: 1} continue } // Get function info funcInfo := GetFuncLookup(field.Function) if funcInfo == nil { return nil, errors.New("invalid function, " + field.Function + " does not exist") } // Call function value value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo) if err != nil { return nil, err } if _, ok := value.([]byte); ok { // If it's a slice, unmarshal it into an interface var val any err := json.Unmarshal(value.([]byte), &val) if err != nil { return nil, err } value = val } v[i] = &jsonKeyVal{Key: field.Name, Value: value} } // Marshal into bytes if jo.Indent { j, _ := json.MarshalIndent(v, "", " ") return j, nil } j, _ := json.Marshal(v) return j, nil } if jo.Type == "array" { // Make sure you set a row count if jo.RowCount <= 0 { return nil, errors.New("must have row count") } v := make([]jsonOrderedKeyVal, jo.RowCount) for i := 0; i < int(jo.RowCount); i++ { vr := make(jsonOrderedKeyVal, len(jo.Fields)) // Loop through fields and add to them to map[string]any for ii, field := range jo.Fields { if field.Function == "autoincrement" { vr[ii] = &jsonKeyVal{Key: field.Name, Value: i + 1} // +1 because index starts with 0 continue } // Get function info funcInfo := GetFuncLookup(field.Function) if funcInfo == nil { return nil, errors.New("invalid function, " + field.Function + " does not exist") } // Call function value value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo) if err != nil { return nil, err } if _, ok := value.([]byte); ok { // If it's a slice, unmarshal it into an interface var val any err := json.Unmarshal(value.([]byte), &val) if err != nil { return nil, err } value = val } vr[ii] = &jsonKeyVal{Key: field.Name, Value: value} } v[i] = vr } // Marshal into bytes if jo.Indent { j, _ := json.MarshalIndent(v, "", " ") return j, nil } j, _ := json.Marshal(v) return j, nil } return nil, errors.New("invalid type, must be array or object") } func addFileJSONLookup() { AddFuncLookup("json", Info{ Display: "JSON", Category: "file", Description: "Format for structured data interchange used in programming, returns an object or an array of objects", Example: `[ { "first_name": "Markus", "last_name": "Moen", "password": "Dc0VYXjkWABx" }, { "first_name": "Osborne", "last_name": "Hilll", "password": "XPJ9OVNbs5lm" }, { "first_name": "Mertie", "last_name": "Halvorson", "password": "eyl3bhwfV8wA" } ]`, Output: "[]byte", ContentType: "application/json", Params: []Param{ {Field: "type", Display: "Type", Type: "string", Default: "object", Options: []string{"object", "array"}, Description: "Type of JSON, object or array"}, {Field: "rowcount", Display: "Row Count", Type: "int", Default: "100", Description: "Number of rows in JSON array"}, {Field: "indent", Display: "Indent", Type: "bool", Default: "false", Description: "Whether or not to add indents and newlines"}, {Field: "fields", Display: "Fields", Type: "[]Field", Description: "Fields containing key name and function to run in json format"}, }, Generate: func(r *rand.Rand, m *MapParams, info *Info) (any, error) { jo := JSONOptions{} typ, err := info.GetString(m, "type") if err != nil { return nil, err } jo.Type = typ rowcount, err := info.GetInt(m, "rowcount") if err != nil { return nil, err } jo.RowCount = rowcount indent, err := info.GetBool(m, "indent") if err != nil { return nil, err } jo.Indent = indent fieldsStr, err := info.GetStringArray(m, "fields") if err != nil { return nil, err } // Check to make sure fields has length if len(fieldsStr) > 0 { jo.Fields = make([]Field, len(fieldsStr)) for i, f := range fieldsStr { // Unmarshal fields string into fields array err = json.Unmarshal([]byte(f), &jo.Fields[i]) if err != nil { return nil, err } } } 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 }