Search code examples
ballerinadynamic-functionballerina-swan-lake

Error when calling a function having a record with float fields as an arg using `function:call` method


isolated function mockFunction(record {|string operation; float operand1; float operand2;|} input) returns string{
    return "mockFunction scuccessful";
}

public function main() returns error? {
    string input = "{\"operation\":\"ADDITION\",\"operand1\":23.0,\"operand2\":43.0}";
    map<json> & readonly inputJson = check input.fromJsonStringWithType();
    any|error observation = function:call(mockFunction, inputJson);
    io:println(observation);
}

I got the following error when I tried the above.

error: {ballerina/lang.function}IncompatibleArguments {"message":"arguments of incompatible types: argument list '(map<(json & readonly)> & readonly)' cannot be passed to function expecting parameter list '(ai.agent:record {| string operation; float operand1; float operand2; |})'"} 

Solution

  • function:call() method will attempt to cast the inputJson to the expected argument type. It gives the above error when the casting fails.

    fromJsonStringWithType https://lib.ballerina.io/ballerina/lang.value/0.0.0#fromJsonStringWithType is a combination of fromJsonString and fromJsonWithType. Numbers are generally converted to decimal with fromJsonString https://lib.ballerina.io/ballerina/lang.value/0.0.0#fromJsonString.

    Numbers in the JSON string are converted into Ballerina values of type decimal except in the following two cases: if the JSON number starts with - and is numerically equal to zero, then it is converted into float value of -0.0; otherwise, if the JSON number is syntactically an integer and is in the range representable by a Ballerina int, then it is converted into a Ballerina int. A JSON number is considered syntactically an integer if it contains neither a decimal point nor an exponent.

    Therefore, in this case, operand1 and operand2 are decimal and not float. This is why the cast fails.

    io:println(inputJson is record {|string operation; float operand1; float operand2;|}); // false
    io:println(inputJson is record {|string operation; decimal operand1; decimal operand2;|}); // true
    

    The solution is to provide the exact type when doing the fromJsonStringWithType

    record {|string operation; float operand1; float operand2;|} inputRecord = check input.fromJsonStringWithType();
    

    fromJsonStringWithType will creates a new value doing the numeric conversions, not a cast. Here the fromJsonString step will still result in decimal values for these fields, but the fromJsonWithType step will handle the numeric conversion when cloning.