@wldomiciano, é difícil responder a essa pergunta.
Eu não tenho um argumento que tenha sido objeto de pesquisa, ou outra referência, é uma experiência/observação particular.
Então posso apenas levantar novos questionamentos.
Assim, tomemos como ponto de partida a consulta ao banco de dados.
É uma boa prática trazer mais dados que o necessário em uma consulta para que o objeto persistido seja semanticamente equivalente ao json?
É uma boa prática criar vários DTOs para atender as consultas customizadas?
Ao criar um DTO, não se perde a compatibilidade semântica entre o objeto original e o consultado?
O correto não seria a ferramenta servir ao desenvolvedor e não o desenvolvedor à ferramenta?
Como dito, eu não tenho uma boa argumentação em relação a afirmação realizada, mas tenho perguntas que me levam a questionar se não seria uma má prática enviar objetos emaranhados para o cliente desenrolar.
Quais contextos?
Eu posso levantar a questão da padronização.
Se o cliente e o servidor compreendem a padronização, a comunicação é mais suave.
Isso é interessante pois no presente tópico não existe uma padronização:
Além disso é mais fácil ler e percorrer uma matriz/tabela do que um mapeamento.
Cabe esclarecer que a tabela proposta é parte de uma response composta por mais chaves.
Fiz um mock (58 linhas) de uma consulta mais complexa, cuja saida no Postmam é a seguinte:
{
"details": {
"etc": "etc",
"results": 2
},
"table": {
"colsNames": [
"id_compra",
"date",
"valueVisualize",
"dataRegister",
"name",
"typeName",
"total",
{
"colsNamesComprador": [
"id",
"nome",
"idade",
"sexo"
]
}
],
"rows": [
[
23423,
"11/09/2014",
"0341",
"11/09/2014",
"08",
"Inter-Prestadora",
342,
[
1,
"jose",
45,
"M"
]
],
[
23478,
"11/09/2012",
"0342",
"11/09/2012",
"08",
"Inter-Prestadora",
349,
[
2,
"maria",
23,
"F"
]
]
]
},
"status": "SUCCESS"
}
scripts auxiliares:
function asString(json){
return JSON.stringify(json);
}
function parseJson(data){
return JSON.parse(data);
}
function Response(data){
data = parseJson(data);
let response = {
details: data.details,
status: data.status,
succes: data.status == "SUCCESS",
notFound: data.status == "NOT_FOUND",
}
if(data.table){
response.colsNames = data.table.colsNames;
response.rows = data.table.rows;
}
return response;
}
function Comprador(data){
let comprador = {
"id": data[0],
"nome":data[1],
"idade":data[2],
"sexo":data[3]
}
return comprador;
}
if(responseCode.code >= 200 && responseCode.code <= 299){
let response = Response(responseBody);
let rows = response.rows;
rows.forEach((row) => {
let comprador = Comprador(row[7]);
console.log(asString(comprador));
});
}
Então, a resposta para a utilização em outros contextos é:
depende de como os dados são manipulados.
Por exemplo, a função que extrai os dados do comprador e retorna um JSON.

Método que realizava o mock no servidor:
@JSONMapping("/complex")
public Response complex(){
JSONObject details = new JSONObject();
details.put("results", 2);
details.put("etc", "etc");
Response response = Response.success(details);
response.setColsNames("id_compra", "date", "valueVisualize", "dataRegister", "name", "typeName", "total",
response.subColsNames("colsNamesComprador", "id","nome","idade","sexo")
);
response.addRow(23423, "11/09/2014", "0341", "11/09/2014", "08", "Inter-Prestadora", 342,
response.subRow(1, "jose", 45,"M")
);
response.addRow(23478, "11/09/2012", "0342", "11/09/2012", "08", "Inter-Prestadora", 349,
response.subRow(2, "maria", 23,"F")
);
return response.pack();
}
Há alguns princípios mas o espaço não seria conveniente, mas no geral é só isso mesmo, a adoção de uma arquitetura.