Using virtual columns like this encodes the “normalizing” lens into the SQL schema in a purely-functional way. This seems like a pure win to me for clients that need to cache data but never update it. The client code can be a “dumb pipe” on the write side and just dump whatever received data into the SQL database. In essence, you are getting CQRS (command/query representation separation) for “free”, built into the storage system. Why spend the engineering cycles in the SQL client?
For a real world example - at Notion we have a SQLite database running in our client apps. Most of the rows come from the back-end API and serve as a local cache. There are some object properties the clients need to query, and other object properties the clients don’t care to query, but are needed to render the data in the UI, so it still needs to be stored locally. Over time, those needs change — as do the shape of the upstream API data source. So, we put un-queried object properties into catch-all JSON columns.
When we introduce a new query pattern to the client, we can run a number of different migration strategies to extract existing data from the JSON column and put it into a newly created column, or use a virtual column/index. Having the catch-all JSON column also means we can add an object property in our backend for web clients, and then later when we roll the feature out to native apps the data is already there - it just needs a migration to make it efficient to query.