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.
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:
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:
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.