Skip to content

Campos com ponto no nome são ignorados silenciosamente na importação #265

Description

@Magnele

Fix: Campos com ponto no nome são ignorados silenciosamente na importação

Resumo

Quando um campo de "TDataSet" possui ponto no nome (ex.: "Id. Mov.", "Nº. Doc.", "Cód. Tp. Mov."), a importação JSON → dataset ignora esse campo silenciosamente, deixando-o vazio no dataset de destino. A causa raiz é o método "TJSONObject.TryGetValue", que realiza navegação hierárquica por caminho usando "." como separador, em vez de fazer uma busca exata pelo nome da chave.


Causa raiz

"TJSONObject.TryGetValue(const APath: string; out AValue: T)" na "System.JSON" do Delphi divide "APath" pelo caractere "'.'" e navega a hierarquia JSON:

"""pascal
// RTL Delphi — System.JSON (simplificado)
LPaths := APath.Split(['.']); // "Id. Mov." → ["Id", " Mov", ""]
for I := 0 to High(LPaths) do
LObj := TJSONObject(LObj).GetValue(LPaths[I]); // falha em " Mov"
"""

Portanto, "TryGetValue('Id. Mov.', LJSONValue)" sempre retorna "False" para qualquer chave que contenha um ponto — mesmo que a chave exista literalmente no objeto JSON.

"TJSONObject.GetValue(const AName: string): TJSONValue", por outro lado, realiza uma varredura linear por nome exato — sem divisão, sem navegação — e encontra "Id. Mov." corretamente.


Estado atual no repositório

O "master" atual já adicionou "GetValue" como fallback de último recurso:

"""pascal
if not AJSONObject.TryGetValue(FormatCaseNameDefinition(LField.FieldName), LJSONValue) then
AJSONObject.TryGetValue(LField.FieldName, LJSONValue);
if not Assigned(LJSONValue) then
begin
// In case the JSON key name has dots, the native method TryGetValue doesn't find it
LJSONValue := AJSONObject.GetValue(LField.FieldName);
if not Assigned(LJSONValue) then
Continue;
end;
"""

Por que ainda está incompleto

O fallback com "GetValue" tenta apenas "LField.FieldName" (o nome original/bruto). Ele nunca tenta o nome formatado produzido por "FormatCaseNameDefinition".

Isso significa que o fix falha sempre que o servidor serializa com transformação de capitalização (ex.: "CaseNameDefinition = cndLower") e o nome do campo também contém ponto:

Chave no JSON do servidor "FormatCaseNameDefinition(FieldName)" "FieldName" Resultado
"id. mov." "id. mov." (cndLower) "Id. Mov." ❌ "GetValue("Id. Mov.")" → nil
"Id. Mov." "Id. Mov." (cndNone) "Id. Mov." ✅ "GetValue("Id. Mov.")" → encontrado

Somente "cndNone" (sem transformação de capitalização) é resgatado pelo fallback atual.


Fix proposto

Substituir ambas as chamadas "TryGetValue" no loop de importação por "GetValue", tentando primeiro o nome formatado e usando o nome original como fallback. Isso elimina completamente o problema do ponto e funciona para todos os valores de "CaseNameDefinition":

"""pascal
// DataSet.Serialize.Import.pas — JSONObjectToDataSet (bloco Delphi)

// ANTES
if not AJSONObject.TryGetValue(TDataSetSerializeUtils.FormatCaseNameDefinition(LField.FieldName), LJSONValue) then
AJSONObject.TryGetValue(LField.FieldName, LJSONValue);
if not Assigned(LJSONValue) then
begin
// In case the JSON key name has dots, the native method TryGetValue doesn't find it
LJSONValue := AJSONObject.GetValue(LField.FieldName);
if not Assigned(LJSONValue) then
Continue;
end;

// DEPOIS
// GetValue faz busca por nome exato, evitando que TryGetValue interprete o ponto
// como separador de caminho JSON. Qualquer FieldName com ponto literal
// (ex.: "Id. Mov.", "Nº. Doc.") é encontrado corretamente em todas as configurações
// de CaseNameDefinition.
LJSONValue := AJSONObject.GetValue(TDataSetSerializeUtils.FormatCaseNameDefinition(LField.FieldName));
if not Assigned(LJSONValue) then
LJSONValue := AJSONObject.GetValue(LField.FieldName);
if not Assigned(LJSONValue) then
Continue;
"""

Por que "GetValue" é seguro aqui

"TJSONObject.GetValue(const AName: string)" itera sobre "Pairs" comparando por igualdade exata de string. Não há tratamento especial para ".", "[" ou qualquer outro caractere. Não há perda de funcionalidade: a única capacidade removida é a navegação por caminho que era involuntária e nunca necessária neste contexto — nomes de campos são sempre planos; datasets aninhados são tratados separadamente como campos "TJSONArray".


Análise de impacto

Todas as demais chamadas "TryGetValue" em "DataSet.Serialize.Import.pas" não são afetadas:

Local Origem da chave Ponto possível? Ação necessária
"'object_state'" / "'objectState'" Constantes internas da biblioteca Não Nenhuma
"LJSONValue.TryGetValue" Chamado em "TJSONValue", não em "TJSONObject" — sem navegação por caminho N/A Nenhuma
Busca de dataset aninhado ("LNestedDataSet.Name") Nomes de componentes Delphi (identificadores válidos) Não Nenhuma
Validação de campos obrigatórios ("LFieldName") Mesmo problema latente, mas afeta apenas campos "Required" Latente Follow-up opcional
Busca de campo-chave para "Locate" em modo merge Nomes de colunas de chave primária são identificadores técnicos Improvável Nenhuma
Desserialização de metadados de campo (linhas 711–748) Constantes internas da biblioteca ("field_name", "data_type" …) Não Nenhuma

Cenário de reprodução

"""pascal
// Servidor: query PostgreSQL com aliases amigáveis para UI
SELECT
mf.id_movifin "Id. Mov.",
mf.n_doc_movifin "Nº. Doc.",
tpmov.cod_tipo "Cód. Tp. Mov."
FROM fintbl_movifin mf ...

// Serializado com DataSet.Serialize (cndNone, RemoveBlankSpaceFieldName = False):
// [{ "Id. Mov.": 2216, "Nº. Doc.": "545454", "Cód. Tp. Mov.": "00022" }]

// Cliente: TFDMemTable.LoadFromJSON(response)
// Sem o fix → "Id. Mov.", "Nº. Doc.", "Cód. Tp. Mov." ficam vazios
// Com o fix → valores populados corretamente
"""


Nota de configuração adicional

Para que nomes de campos com espaços sobrevivam ao ciclo de serialização/desserialização,
configure também:

"""pascal
TDataSetSerializeConfig.GetInstance.RemoveBlankSpaceFieldName := False;
"""

O padrão é "True", que remove os espaços e produz chaves como "Id.Mov." — ainda quebradas pela navegação por caminho do "TryGetValue", e também ilegíveis como captions de UI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions