Gutenberg default value not saved in json

When I set default values in my attributes for my Gutenberg blocks,
the values can be consumed on the client, but it does not appear in
the JSON data until I change the attribute with setAttributes.

When we register a block type (think of it like a post type) which happens on each page load, the block type data (title, default attributes/values, etc.) are saved (in memory) via the block types data source (namespaced core/blocks), and that data is available to the client for as long as the page is opened, hence that’s why the values can be consumed on the client despite the default attribute values are not in that JSON data (which is persistent and part of the post content that’s saved in the database).

Therefore it’s actually normal for Gutenberg (or the block editor) to not save the default attribute values to the JSON data (in the block’s comment delimiter), because the block editor can always read the values from the block type data in the block types data source. And for example, via the browser console, you can try this:

// Inspect the attributes for the core Image block TYPE.
wp.data.select( 'core/blocks' ).getBlockType( 'core/image' ).attributes

And you’d see that the returned data is always the same (e.g. linkDestination in the above example, has its default set to none) and always accessible throughout the entire page via wp.data (see @wordpress/data).

So don’t worry when the default values do not appear in the JSON data — and you should also know that if you input a value that’s same as the default one, then it won’t be saved to the JSON data. I.e. Only custom values are supposed to be saved there.

// Let's assume your block type's name is my-plugin/foo-block, with the attribute
// named infoButtonText which defaults to 'I am default'.

setAttributes( { infoButtonText: 'I am NOT default' } ); // using custom value
// Result: <!-- wp:my-plugin/foo-block {"infoButtonText":"I am NOT default"} /-->

setAttributes( { infoButtonText: 'I am default' } );     // using default value
// Result: <!-- wp:my-plugin/foo-block /-->
// I.e. The infoButtonText is no longer in the JSON data.
// But attributes.infoButtonText would actually return 'I am default' - the default value.