Nulls are being rendered in the latest JSON (32.0/33.0). You must explicitly the set the value to null for it to work. Consider the following code:
Account a = new Account(Name='test',Industry=null);
System.debug(LoggingLevel.ERROR, JSON.serialize(a));
Output:
{"attributes":{"type":"Account"},"Name":"test","Industry":null}
If you need every single field, you'll have to do something like:
Account a = new Account();
for(SObjectField f: account.sobjecttype.getdescribe().fields.getmap().values()) {
try { a.put(f, null); } catch(exception e) { }
}
Even better, you could always just use a Map, which avoids the errors of field editability:
Map<String, Object> a = new Map<String, Object>();
SObject b = [SELECT Id, Name FROM Account LIMIT 1];
for(SObjectField f: account.sobjecttype.getdescribe().fields.getmap().values()) {
try { a.put(String.valueOf(f), b.get(f)); }
catch(Exception e) { a.put(String.valueOf(f), null); }
}
System.debug(logginglevel.error, json.serialize(a));
I've just tried this code in executeAnonymous and came up with fully realized JSON. Note that I use a try-catch here because queried SObjects generate errors for missing fields. If you're using a manually constructed SObject, you'll not have to worry about catching errors.
Finally, note that executeAnonymous tends to always run in the latest version. You won't normally see differences in execution contexts because the entire transaction is set to version 32.0 mode when running executeAnonymous. To emulate a lower version, you'd have to write a Visualforce page and set the version of the page to a lower value, and then also set the controller's version to that lower value, etc...
Alternatively, you could write a REST class, and call that REST function. That should also set the version correctly. Since executeAnonymouse is basically an eval(), it tends to behave in the latest version, because it has to be compiled on demand.
This answer is not intended to teach you everything about writing unit tests, nor to specifically answer every question, but to provide a quick summary and links to the resources that will help you move forward and develop more specific questions that SFSE can assist with.
Overview
Unit (and integration) testing is a big topic, but it starts with a small set of principles. If you've never written a unit test before, we strongly encourage you to complete the Unit Testing on the Lightning Platform Trailhead module and read at least the Month of Testing series. These materials and others are linked under Resources, below.
Fundamentally, testing comprises three steps, all of which take place within the context of a unit test:
- Creating test data as input for your code, which is designed to ensure that a specific logical path is executed. This can take the form of values in memory or of creating and inserting sObjects.
- Executing that code, meaning that that specific code path runs within a method annotated with the
@isTest
annotation.
- Writing assertions to demonstrate that the results of the code are correct and as expected for the given inputs.
Then, all code that is executed under step (2) is counted as covered under Salesforce's code coverage metrics. Code coverage is a side effect of high quality unit tests. Salesforce uses code coverage as a proxy to measure the presence of unit tests in your deployments.
Unit testing principles are quite general, and most Apex code is not special in the sense of requiring unique approaches to create a successful test. Techniques for implementing tests that perform all three steps are taught in the resources we include below.
Test Isolation
On Salesforce, all unit tests are executed in an isolated context. In this context, your code cannot see data in your organization, including ordinary records as well as Custom Settings. All data must be created via the unit test or @testSetup
method.
Metadata records, including Users and Custom Metadata, are visible in test context.
An older annotation, seeAllData=true
, allows tests to see all data in the Salesforce org. Use of this annotation is strongly discouraged for new unit tests, and is considered a very bad practice. It's important instead to follow the first step above, by designing test data as input for your code. This practice makes unit tests self-contained and repeatable, and insulates them against fragility stemming from data changes.
Smoke Tests (Tests without Assertions)
Unit tests that don't contain assertions are often called smoke tests. These tests have very limited value, because they show nothing other than that your code does not crash under a specific set of circumstances. They don't prove the code works or does what it's intended to do.
Resources
Trailhead
Apex Developer Guide
- Testing Apex
- The
Test
class reference, which includes a variety of testing-related utility methods, including Test.stopTest()
and Test.startTest()
, as well as methods for setting static SOSL results, controlling audit fields, working with mocks and stubs, and other tools.
Blogs and Articles
- Month of Testing series from the Salesforce Developers blog.
Dreamforce Video Content
Third-Party Testing Frameworks (Advanced Topics)
- ApexMocks, an open-source mocking framework for Apex.
- Force-DI, a framework for pervasive dependency injection.
Best Answer
Apex provides multiple routes to achieving JSON serialization and deserialization of data structures. This answer summarizes use cases and capabilities of untyped deserialization, typed (de)serialization, manual implementations using
JSONGenerator
andJSONParser
, and tools available to help support these uses. It is not intended to answer every question about JSON, but to provide an introduction, overview, and links to other resources.Summary
Apex can serialize and deserialize JSON to strongly-typed Apex classes and also to generic collections like
Map<String, Object>
andList<Object>
. In most cases, it's preferable to define Apex classes that represent data structures and utilize typed serialization and deserialization withJSON.serialize()
/JSON.deserialize()
. However, some use cases require applying untyped deserialization withJSON.deserializeUntyped()
.The
JSONGenerator
andJSONParser
classes are available for manual implementations and should be used only where automatic (de)serialization is not practicable, such as when keys in JSON are reserved words or invalid identifiers in Apex, or when low-level access is required.The key documentation references are the
JSON
class in the Apex Developer Guide and the section JSON Support. Other relevant documentation is linked from those pages.Complex Types in Apex and JSON
JSON offers maps (or objects) and lists as its complex types. JSON lists map to Apex
List
objects. JSON objects can map to either Apex classes, with keys mapping to instance variables, or ApexMap
objects. Apex classes and collections can be intermixed freely to construct the right data structures for any particular JSON objective.Throughout this answer, we'll use the following JSON as an example:
This JSON includes two levels of nested objects, as well as a list of primitive values.
Typed Serialization with
JSON.serialize()
andJSON.deserialize()
The methods
JSON.serialize()
andJSON.deserialize()
convert between JSON and typed Apex values. When usingJSON.deserialize()
, you must specify the type of value you expect the JSON to yield, and Apex will attempt to deserialize to that type.JSON.serialize()
accepts both Apex collections and objects, in any combination that's convertible to legal JSON.These methods are particularly useful when converting JSON to and from Apex classes, which is in most circumstances the preferred implementation pattern. The JSON example above can be represented with the following Apex class:
To parse JSON into an
Example
instance, executeAlternately, to convert an
Example
instance into JSON, executeNote that nested JSON objects are modeled with one Apex class per level of structure. It's not required for these classes to be inner classes, but it is a common implementation pattern. Apex only allows one level of nesting for inner classes, so deeply-nested JSON structures often convert to Apex classes with all levels of structure defined in inner classes at the top level.
JSON.serialize()
andJSON.deserialize()
can be used with Apex collections and classes in combination to represent complex JSON data structures. For example, JSON that storedExample
instances as the values for higher-level keys:can be serialized from, and deserialized to, a
Map<String, Example>
value in Apex.It should be noted that this approach will not work where the JSON to be deserialized cannot be directly mapped to Apex class attributes (e.g. because the JSON property names are Apex reserved words or are invalid as Apex identifiers (e.g. contain hyphens or other invalid characters).
For more depth on typed serialization and deserialization, review the
JSON
class documentation. Options are available for:null
valuesUntyped Deserialization with
JSON.deserializeUntyped()
In some situations, it's most beneficial to deserialize JSON into Apex collections of primitive values, rather than into strongly-typed Apex classes. For example, this can be a valuable approach when the structure of the JSON may change in ways that aren't compatible with typed deserialization, or which would require features that Apex does not offer like algebraic or union types.
Using the
JSON.deserializeUntyped()
method yields anObject
value, because Apex doesn't know at compile time what type of value the JSON will produce. It's necessary when using this method to typecast values pervasively.Take, for example, this JSON, which comes in multiple variants tagged by a
"scope"
value:or
JSON input that varies in this way cannot be handled with strongly-typed Apex classes because its structure is not uniform. The values for the keys
scope
anddata
have different types.This kind of JSON structure can be deserialized using
JSON.deserializeUntyped()
. That method returns anObject
, an untyped value whose actual type at runtime will reflect the structure of the JSON. In this case, that type would beMap<String, Object>
, because the top level of our JSON is an object. We could deserialize this JSON viaThe untyped nature of the value we get in return cascades throughout the structure, because Apex doesn't know the type at compile time of any of the values (which may, as seen above, be heterogenous) in this JSON object.
As a result, to access nested values, we must write defensive code that inspects values and typecasts at each level. The example above will throw a
TypeException
if the resulting type is not what is expected.To access the data for the first element in the above JSON, we might do something like this:
While there are other ways of structuring such code, including catching
JSONException
andTypeException
, the need to be defensive is a constant. Code that fails to be defensive while working with untyped values is vulnerable to JSON changes that produce exceptions and failure modes that won't manifest in many testing practices. Common exceptions includeNullPointerException
, when carelessly accessing nested values, andTypeException
, when casting a value to the wrong type.Manual Implementation with
JSONGenerator
andJSONParser
The
JSONGenerator
andJSONParser
classes allow your application to manually construct and parse JSON.Using these classes entails writing explicit code to handle each element of the JSON. Using
JSONGenerator
andJSONParser
typically yields much more complex (and much longer) code than using the built-in serialization and deserialization tools. However, it may be required in some specific applications. For example, JSON that includes Apex reserved words as keys may be handled using these classes, but cannot be deserialized to native classes because reserved words (liketype
andclass
) cannot be used as identifiers.As a general guide, use
JSONGenerator
andJSONParser
only when you have a specific reason for doing so. Otherwise, strive to use native serialization and deserialization, or use external tooling to generate parsing code for you (see below).Generating Code with
JSON2Apex
JSON2Apex is an open source Heroku application. JSON2Apex allows you to paste in JSON and generates corresponding Apex code to parse that JSON. The tool defaults to creating native classes for serialization and deserialization. It automatically detects many situations where explicit parsing is required and generates
JSONParser
code to deserialize JSON to native Apex objects.JSON2Apex does not solve every problem related to using JSON, and generated code may require revision and tuning. However, it's a good place to start an implementation, particularly for users who are just getting started with JSON in Apex.
Common Workarounds
JSON attribute is a reserved word or invalid identifier
For example, you might have incoming JSON that looks like:
that you want to deserialize into a custom Apex Type:
But
currency
can't be used as a variable name because it is a reserved word, nor can_mode
because it is not a legal Apex identifier.One easy workaround is to rename the variable and preprocess the JSON before deserializing:
However, note that this strategy can fail on large payloads. JSON2Apex is capable of generating manual deserialization code that handles invalid identifiers as well, and untyped deserialization is another option.