wp_localize_script() exists to pass translated strings to JavaScript and has been used for everything else because it was the only way to get data across. 4.5 added the function that says what it means.
wp_add_inline_script(
'shop-app',
'window.shopConfig = ' . wp_json_encode( array(
'apiRoot' => esc_url_raw( rest_url( 'shop/v1' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
) ) . ';',
'before'
);
The practical difference is that localize casts everything to string, so an integer arrives as "5" and a boolean as "1" or "" — which every JavaScript consumer then has to undo. wp_json_encode preserves types. The before position matters: after the script has run, the variable is too late.