Repository: DiceDB/dice
File: internal/eval/eval.go (Go)

Code before:
            pathArrData = append(pathArrData, value)
        }
        obj.Value = pathArrData
        resultsArray = append(resultsArray, int64(len(pathArrData)))
    } else {
        expr.MustModify(jsonData, func(data any) (interface{}, bool) {
            pathArrData, ok := data.([]interface{})
            if(!ok) {
                resultsArray = append(resultsArray, clientio.RespNIL)
                return data, false
            }

            for _, arg := range args[2:] {
                var value interface{}
                var conversionErr error

                // Determine type of element in the existing array (if any) to match type
                if len(pathArrData) > 0 {
                    switch utils.GetJSONFieldType(pathArrData[0]) {
                    case utils.StringType:
                        value = arg
                    case utils.NumberType:
                        value, conversionErr = strconv.ParseFloat(arg, 64)
                    case utils.IntegerType:
                        var intVal int64
                        intVal, conversionErr = strconv.ParseInt(arg, 10, 64)
                        value = intVal
                    case utils.ArrayType:
                        var arrObject []interface{}
                        conversionErr = sonic.Unmarshal([]byte(arg), &arrObject)
                        if conversionErr == nil {
                            value = arrObject
                        }
                    case utils.ObjectType:
                        var jsonObject map[string]interface{}
                        conversionErr = sonic.Unmarshal([]byte(arg), &jsonObject)
                        if conversionErr == nil {
                            value = jsonObject
                        }
                    default:
                        resultsArray = append(resultsArray, diceerrors.NewErrWithMessage("failed to convert existing value in array to a valid json field type"))
                        return data, false
                }
                } else {
                    // If array is empty, default interface from sonic.UnmarshalString
                    var defaultObject interface{}
                    conversionErr = sonic.UnmarshalString(arg, &defaultObject)
                    if conversionErr == nil {
                        value = defaultObject
                    }
                }

Review comment (bug):
I feel this implementation can be simplified a lot. I tried coding it on my machine and I think you can also do something similar here. I tested it and it works fine for the most part. It also allows you to have arrays of mixed data types (while maintaining type information)

Note that this is just a rough implementation, please match the error messages exactly with how redis does them (and add tests for each one):

```go
func evalJSONARRAPPEND(args []string, store *dstore.Store) []byte {
	if len(args) < 3 {
		return diceerrors.NewErrArity("JSON.ARRAPPEND")
	}

	key := args[0]
	path := args[1]
	values := args[2:]

	obj := store.Get(key)
	if obj == nil {
		return diceerrors.NewErrWithMessage("ERR key does not exist")
	}

	err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeJSON, object.ObjEncodingJSON)
	if err != nil {
		return err
	}

	jsonData := obj.Value

	expr, parseErr := jp.ParseString(path)
	if parseErr != nil {
		return diceerrors.NewErrWithMessage("ERR invalid JSONPath")
	}

	// Parse the input values as JSON
	parsedValues := make([]interface{}, len(values))
	for i, v := range values {
		var parsedValue interface{}
		err := sonic.UnmarshalString(v, &parsedValue)
		if err != nil {
			return diceerrors.NewErrWithMessage(fmt.Sprintf("ERR invalid JSON value: %s", v))
		}
		parsedValues[i] = parsedValue
	}

	var resultsArray []interface{}
	modified := false

	// Capture the modified data when modifying the root path
	var newData interface{}
	var modifyErr error

	newData, modifyErr = expr.Modify(jsonData, func(data any) (interface{}, bool) {
		arr, ok := data.([]interface{})
		if !ok {
			// Not an array
			resultsArray = append(resultsArray, nil)
			return data, false
		}

		// Append the parsed values to the array
		arr = append(arr, parsedValues...)

		// Record the new length
		resultsArray = append(resultsArray, int64(len(arr)))

		modified = true

		return arr, modified
	})

	if modifyErr != nil {
		return diceerrors.NewErrWithMessage(fmt.Sprintf("ERR failed to modify JSON data: %v", modifyErr))
	}

	if !modified {
		// If no modification was made, it means the path did not exist or was not an array
		return clientio.Encode([]interface{}{nil}, false)
	}

	jsonData = newData

	obj.Value = jsonData

	return clientio.Encode(resultsArray, false)
}
```

Code after:
		if err != nil {
			return diceerrors.NewErrWithMessage("invalid JSONPath")
		}

		err = expr.Set(rootData, jsonValue)
		if err != nil {
			return diceerrors.NewErrWithMessage("failed to set value")
		}
	} else {
		// If path is root, replace the entire JSON
		rootData = jsonValue
	}

	// Create a new object with the updated JSON data
	newObj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON)
	store.Put(key, newObj)
	return clientio.RespOK
}

// evalJSONARRAPPEND appends the value(s) provided in the args to the given array path
// in the JSON object saved at key in arguments.
// Args must contain atleast a key, path and value.
// If the key does not exist or is expired, it returns response.RespNIL.
// If the object at given path is not an array, it returns response.RespNIL.
// Returns the new length of the array at path.
func evalJSONARRAPPEND(args []string, store *dstore.Store) []byte {
	if len(args) < 3 {
		return diceerrors.NewErrArity("JSON.ARRAPPEND")
	}

	key := args[0]
	path := args[1]
	values := args[2:]

	obj := store.Get(key)
	if obj == nil {
		return diceerrors.NewErrWithMessage("ERR key does not exist")
	}

	errWithMessage := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeJSON, object.ObjEncodingJSON)
	if errWithMessage != nil {
		return errWithMessage
	}

	jsonData := obj.Value

	expr, err := jp.ParseString(path)
	if err != nil {
		return diceerrors.NewErrWithMessage(fmt.Sprintf("ERR Path '%s' does not exist or not an array", path))
	}