How should I handle cases like this where the documentation for Bitcoin Core states one thing, and the actual program does something else?
In the example RPC results, it will show whether the result is a JSON array by including brackets [] or it will show if it is a JSON object (associative array) by using curly brackets {}. If the result does not come with any JSON encoding and is a single string or other data type, then you will not see that in the example.

You can see an example of a result that comes in the form of a JSON array by using listlockunspent:

Result:
[
  {
    "txid" : "transactionid",     (string) The transaction id locked
    "vout" : n                      (numeric) The vout value
  }
  ,...
]


An example of a JSON object using createwallet:

Result:
{
  "name" :    &lt;wallet_name&gt;,        (string) The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path.
  "warning" : &lt;warning&gt;,            (string) Warning message if wallet was not loaded cleanly.
}


An example of a return value that is not JSON encoded using getbalance:

Result:
amount              (numeric) The total amount in BTC received for this wallet.


I believe you may be mistaken with the assumption that sendtoaddress return a JSON object. The code clearly shows that the result is not JSON encoded:



return tx-&gt;GetHash().GetHex();


That is a transaction object, which retrieves the transaction hash (256 bit opaque blob), which is then converted to a string using the GetHex() method.

The code would use UniValue if it were to encode JSON. For example in the code of createwallet:

UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet-&gt;GetName());
obj.pushKV("warning", warning);

return obj;