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

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

    jsonData := obj.Value

    // Parse the JSONPath expression
    expr, err := jp.ParseString(path)
    if err != nil {
        return diceerrors.NewErrWithMessage("invalid JSONPath")
    }

    // Execute the JSONPath query
    results := expr.Get(jsonData)
    if len(results) == 0 {
        return clientio.Encode([]interface{}{}, false)  // Return empty array if no matches
    }

	toggleResults := make([]interface{}, len(results))

    for i, result := range results {
        switch v := result.(type) {
        case bool:
            newValue := !v
            err := expr.Set(jsonData, newValue)
            if err != nil {
                return diceerrors.NewErrWithMessage("failed to set toggled value")
            }

            // Update the value in the store
            newObj := &object.Obj{
                Value:          jsonData,
                TypeEncoding:   object.ObjTypeJSON,
            }
            store.Put(key, newObj)

            // Return simple integer toggle result (1 for true, 0 for false)
            if newValue {
                toggleResults[i] = 1
            } else {
                toggleResults[i] = 0
            }
        default:
            toggleResults[i] = nil
        }
    }

    return clientio.Encode(toggleResults, false)
}


Review comment (suggestion):
Hey, I am implementing JSON.ARRAPPEND where I also have to modify json for multiple results like this. I had also done this similarly using expr.Set() in the loop, it works fine when there is only 1 value in results array but for example in your case lets say the json is `{"isSimple":true,"nested":{"isSimple":false}}` and you use the toggle command with path **$..isSimple** does it make both of them same or does it toggle both of them separately?
I am facing this issue where its setting the same array which i am getting during the last iteration and expr.Set sets it to all the matching paths not just the one in iteration.

Code after:
	var countClear uint64 = 0
	if len(args) == 1 || path == defaultRootPath {
		if jsonData != struct{}{} {
			// If path is root and len(args) == 1, return it instantly
			newObj := store.NewObj(struct{}{}, -1, object.ObjTypeJSON, object.ObjEncodingJSON)
			store.Put(key, newObj)
			countClear++
			return clientio.Encode(countClear, false)
		}
	}

	expr, err := jp.ParseString(path)
	if err != nil {
		return diceerrors.NewErrWithMessage("invalid JSONPath")
	}

	_, err = expr.Modify(jsonData, func(element any) (altered any, changed bool) {
		switch utils.GetJSONFieldType(element) {
		case utils.IntegerType, utils.NumberType:
			if element != utils.NumberZeroValue {
				countClear++
				return utils.NumberZeroValue, true
			}
		case utils.ArrayType:
			if len(element.([]interface{})) != 0 {
				countClear++
				return []interface{}{}, true
			}
		case utils.ObjectType:
			if element != struct{}{} {
				countClear++
				return struct{}{}, true
			}
		default:
			return element, false
		}
		return
	})
	if err != nil {
		return diceerrors.NewErrWithMessage(err.Error())
	}
	// Create a new object with the updated JSON data
	newObj := store.NewObj(jsonData, -1, object.ObjTypeJSON, object.ObjEncodingJSON)
	store.Put(key, newObj)
	return clientio.Encode(countClear, false)
}

// evalJSONTYPE retrieves a JSON value type stored at the specified key
// args must contain at least the key;  (path unused in this implementation)
// Returns response.RespNIL if key is expired or it does not exist
// Returns encoded error response if incorrect number of arguments