karthikselva's answer is spot-on, that JSON.deserialize
will produce the same result in your use case where taxable
is null
if the field is omitted in either a unit test or in a real API call.
This is not a bug but rather an intended feature of serialization and deserialization. Microsoft explains the concept of "serialization" rather well in its docs.
Serialization allows the developer to save the state of an object and recreate it as needed, providing storage of objects as well as data exchange.
The point of the serialized string is that it represents the exact state of an object, so that when you deserialize the string you get the object exactly as it was when it was serialized. If serialization were to trigger other code that may modify the values of member variables, the point of serialization would be lost.
Proposed solution
In your situation, you could explicitly create the behavior you want by using a getter method as follows, handling the situation where taxable
is null.
public class APIParams {
public String name;
public String category;
/**
* Note that this field is now private.
*/
private Boolean taxable = false;
public Boolean getTaxable() {
return this.taxable == null ? false : this.taxable;
}
public void setTaxable(Boolean value) {
this.taxable = value;
}
}
A modified APIParams example to illustrate serialization behavior
For clarity, let's move the default value assignments into a constructor.
public class APIParams {
public String category;
public String name;
public Boolean taxable;
public APIParams() {
this.name = 'UNKNOWN';
this.category = 'UNKNOWN';
this.taxable = false;
}
}
When constructing new instances of the APIParams
class in Apex, you would normally have to call the APIParams()
constructor, which executes code that sets default values.
Now consider the unit test below.
@IsTest
private class APIParamsTest {
@IsTest
private static void deserializeNameOnly() {
// Given
String jsonBody = '{"name":"Acme"}';
// When
Test.startTest();
APIParams params = (APIParams)JSON.deserialize(jsonBody, APIParams.class);
// Then
Test.stopTest();
System.assertEquals('Acme', params.name, 'name');
System.assertEquals(null, params.category, 'category');
System.assertEquals(null, params.taxable, 'taxable');
}
}
You will see that the constructor is never executed by design.
return taxable == null ? false : taxable;