[SalesForce] Why is String.valueOf(Blob) different that Blob.toString()

I am working on a REST Service that will desiralize the request body and interpret it. My understanding was that calling String.valueOf(objectInstance) would just call objectInstance.toString(). However, I am observing different behavior.

Working

Blob requestBody = Blob.valueOf('[{"key":"value"}]');
List<Object> body = (List<Object>)JSON.deserializeUntyped(requestBody.toString());

Not Working

Blob requestBody = Blob.valueOf('[{"key":"value"}]');
List<Object> body = (List<Object>)JSON.deserializeUntyped(String.valueOf(requestBody));

The above throws:

Line: 11, Column: 1

System.JSONException: Unexpected character ('B' (code 66)): expected a valid value (number, String, array, object, 'true', 'false' or 'null') at input location [1,2]

Best Answer

String.valueOf returns Blob[X] for Blob values. I believe it does it this way because System.debug internally uses String.valueOf, and debugging a potentially non-Unicode stream into a Unicode String could break things. In other words, you can't reliably use String.valueOf unless you can also use it in System.debug and get the output you expect. String.valueOf is always safe to use on a Blob, but Blob.toString may result in an exception if the contents are not a Unicode String.

Let's go with some examples to show how other standard and custom objects behave:

String.valueOf(JSON.createParser('[]'));
// JSONParser:[delegate=common.apex.json.ApexJsonParser@7cde3f62]

String.valueOf(Crypto.generateAesKey(128));
// Blob[16]

String.valueOf([SELECT Id FROM Account LIMIT 1]);
// (Account:{Id=001..., RecordTypeId=012...})

String.valueOf(new MyClassWithoutToString());
// or //
new MyClassWithoutToString().toString();
// MyClasswithoutTostring:[key1=value1, key2=value2]

String.valueOf(new MyClassWithToString());
// Hello There. I work correctly because I have an overridden toString.

String.valueOf(Date.today());    Date.today().format()
// 2016-02-10                    // 2/10/2016

Database.getQueryLocator([SELECT Id FROM Account LIMIT 1])
// Database.QueryLocator[Query=SELECT Id FROM Account LIMIT 1]

String.valueOf(Database.setSavepoint());
// SavepointValue0

String.valueOf(new Component.MyCustomComponent());
// *** Crashes and produces no logs ***

As you can see, you can never quite be sure what you're going to get out of it, unless you're using one of the concrete types listed in the documentation, which is currently: Date, DateTime, Decimal, Integer, and Long.

It does say that it supports "Object" generically, but generally speaking, you'll get the "debug safe" version of a string if you do so, which means it will probably be the wrong value.

Related Topic