fix: Risolve errori di deserializzazione JSON con valori decimali in Salesforce API

- Aggiunge configurazione JsonSerializerOptions per garantire compatibilità con Salesforce
- Implementa normalizzazione automatica di valori numerici con virgola decimale (es. "0,00" → 0.00)
- Sostituisce PostAsJsonAsync con StringContent personalizzato per controllo completo della serializzazione
- Aggiunge metodi NormalizeNumericValues e IsNumericWithComma per conversione formato decimale
- Aggiorna TransformValue per utilizzare InvariantCulture nelle conversioni numeriche
- Applica normalizzazione in CreateEntityAsync e metodi Composite (BatchCreate/BatchUpdate)
- Aggiunge logging dettagliato per tracciare le normalizzazioni numeriche

Risolve: "Impossibile deserializzare l'istanza di double da VALUE_STRING valore 0,00"
- Il problema era causato dall'invio di valori decimali nel formato italiano (virgola)
  invece del formato americano (punto) richiesto da Salesforce API
- La soluzione garantisce che tutti i valori numerici vengano sempre serializzati
  nel formato corretto per Salesforce indipendentemente dalla cultura locale
This commit is contained in:
2025-08-06 00:21:50 +02:00
parent 20a514068a
commit bcc936526b
5 changed files with 143 additions and 11 deletions
+16
View File
@@ -27,6 +27,22 @@
"options": { "options": {
"cwd": "${workspaceFolder}/Data_Coupler" "cwd": "${workspaceFolder}/Data_Coupler"
} }
},
{
"label": "Publish Data_Coupler",
"type": "shell",
"command": "dotnet",
"args": [
"publish",
"--configuration",
"Release",
"--output",
"${workspaceFolder}/publish",
"--project",
"Data_Coupler/Data_Coupler.csproj"
],
"group": "build",
"problemMatcher": []
} }
] ]
} }
@@ -23,6 +23,17 @@ namespace DataConnection.REST.Implementations
private string? _instanceUrl; private string? _instanceUrl;
private DateTime _tokenExpiry; private DateTime _tokenExpiry;
/// <summary>
/// Configurazione JSON per garantire la compatibilità con Salesforce API
/// Utilizza sempre la cultura invariante per i numeri per evitare problemi con virgole/punti decimali
/// </summary>
private static readonly JsonSerializerOptions SalesforceJsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
public SalesforceServiceClient(HttpClient httpClient, RestServiceOptions options) public SalesforceServiceClient(HttpClient httpClient, RestServiceOptions options)
: base(httpClient, options) : base(httpClient, options)
{ {
@@ -392,9 +403,19 @@ namespace DataConnection.REST.Implementations
Console.WriteLine($"--- Salesforce Entity Creation Attempt ---"); Console.WriteLine($"--- Salesforce Entity Creation Attempt ---");
Console.WriteLine($"SObject: {entityName}"); Console.WriteLine($"SObject: {entityName}");
Console.WriteLine($"Target URL: {createUri}"); Console.WriteLine($"Target URL: {createUri}");
Console.WriteLine($"Data: {System.Text.Json.JsonSerializer.Serialize(entityData)}"); Console.WriteLine($"Data: {JsonSerializer.Serialize(entityData, SalesforceJsonOptions)}");
var response = await _httpClient.PostAsJsonAsync(createUri, entityData, cancellationToken); // Normalizza i valori numerici per evitare problemi con virgole decimali
var normalizedData = NormalizeNumericValues(entityData);
// Usa StringContent con configurazione JSON specifica per Salesforce
var jsonContent = new StringContent(
JsonSerializer.Serialize(normalizedData, SalesforceJsonOptions),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await _httpClient.PostAsync(createUri, jsonContent, cancellationToken);
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
@@ -414,7 +435,7 @@ namespace DataConnection.REST.Implementations
return entityData; // Return original data if no response content return entityData; // Return original data if no response content
// Salesforce returns creation result with Id and success status // Salesforce returns creation result with Id and success status
var creationResult = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(responseContent); var creationResult = JsonSerializer.Deserialize<Dictionary<string, object>>(responseContent, SalesforceJsonOptions);
// Merge the original data with the creation result (which includes the new Id) // Merge the original data with the creation result (which includes the new Id)
if (creationResult != null) if (creationResult != null)
@@ -653,6 +674,76 @@ namespace DataConnection.REST.Implementations
return await AuthenticateAsync(cancellationToken); return await AuthenticateAsync(cancellationToken);
} }
/// <summary>
/// Normalizza i valori numerici in un dictionary per garantire compatibilità con Salesforce API
/// Converte valori decimali con virgola in formato con punto decimale
/// </summary>
private static Dictionary<string, object> NormalizeNumericValues(Dictionary<string, object> data)
{
var normalizedData = new Dictionary<string, object>();
bool hasNormalized = false;
foreach (var kvp in data)
{
var value = kvp.Value;
if (value != null)
{
// Se è una stringa che rappresenta un numero decimale con virgola, convertila
if (value is string stringValue && IsNumericWithComma(stringValue))
{
if (decimal.TryParse(stringValue, System.Globalization.NumberStyles.Number,
System.Globalization.CultureInfo.CurrentCulture, out decimal decimalValue))
{
// Converte in double usando cultura invariante per garantire punto decimale
var normalizedValue = double.Parse(decimalValue.ToString(System.Globalization.CultureInfo.InvariantCulture));
normalizedData[kvp.Key] = normalizedValue;
Console.WriteLine($"NUMERIC NORMALIZATION: {kvp.Key}: '{stringValue}' → {normalizedValue}");
hasNormalized = true;
}
else
{
normalizedData[kvp.Key] = value;
}
}
// Se è già un decimal, convertilo in double con cultura invariante
else if (value is decimal dec)
{
var normalizedValue = double.Parse(dec.ToString(System.Globalization.CultureInfo.InvariantCulture));
normalizedData[kvp.Key] = normalizedValue;
Console.WriteLine($"DECIMAL NORMALIZATION: {kvp.Key}: {dec} → {normalizedValue}");
hasNormalized = true;
}
else
{
normalizedData[kvp.Key] = value;
}
}
else
{
normalizedData[kvp.Key] = value!;
}
}
if (hasNormalized)
{
Console.WriteLine($"NORMALIZATION SUMMARY: Processed {data.Count} fields, normalized {normalizedData.Count(kvp => kvp.Value is double)} numeric values");
}
return normalizedData;
}
/// <summary>
/// Verifica se una stringa rappresenta un numero con virgola decimale
/// </summary>
private static bool IsNumericWithComma(string value)
{
if (string.IsNullOrEmpty(value)) return false;
// Pattern per numeri con virgola: opzionale segno, cifre, virgola, cifre
return System.Text.RegularExpressions.Regex.IsMatch(value.Trim(), @"^[+-]?\d+,\d+$");
}
// --- Nested classes for deserializing Salesforce responses --- // --- Nested classes for deserializing Salesforce responses ---
private class SalesforceTokenResponse private class SalesforceTokenResponse
{ {
@@ -928,17 +1019,27 @@ namespace DataConnection.REST.Implementations
for (int i = 0; i < batch.Count; i++) for (int i = 0; i < batch.Count; i++)
{ {
// Normalizza i valori numerici per evitare problemi con virgole decimali
var normalizedData = NormalizeNumericValues(batch[i]);
var subrequest = new SalesforceCompositeSubRequest var subrequest = new SalesforceCompositeSubRequest
{ {
Method = "POST", Method = "POST",
Url = $"/services/data/v60.0/sobjects/{entityName}/", Url = $"/services/data/v60.0/sobjects/{entityName}/",
ReferenceId = $"create_{startIndex + i}", ReferenceId = $"create_{startIndex + i}",
Body = batch[i] Body = normalizedData
}; };
compositeRequest.CompositeRequest.Add(subrequest); compositeRequest.CompositeRequest.Add(subrequest);
} }
var response = await _httpClient.PostAsJsonAsync(compositeUri, compositeRequest, cancellationToken); // Usa StringContent con configurazione JSON specifica per Salesforce
var jsonContent = new StringContent(
JsonSerializer.Serialize(compositeRequest, SalesforceJsonOptions),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await _httpClient.PostAsync(compositeUri, jsonContent, cancellationToken);
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
@@ -956,7 +1057,7 @@ namespace DataConnection.REST.Implementations
} }
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
var compositeResponse = JsonSerializer.Deserialize<SalesforceCompositeResponse>(responseContent); var compositeResponse = JsonSerializer.Deserialize<SalesforceCompositeResponse>(responseContent, SalesforceJsonOptions);
var results = new List<CompositeOperationResult>(); var results = new List<CompositeOperationResult>();
@@ -975,7 +1076,7 @@ namespace DataConnection.REST.Implementations
{ {
if (subResponse.Body is JsonElement bodyElement) if (subResponse.Body is JsonElement bodyElement)
{ {
var bodyDict = JsonSerializer.Deserialize<Dictionary<string, object>>(bodyElement.GetRawText()); var bodyDict = JsonSerializer.Deserialize<Dictionary<string, object>>(bodyElement.GetRawText(), SalesforceJsonOptions);
result.CreatedData = bodyDict; result.CreatedData = bodyDict;
// Extract the created ID // Extract the created ID
@@ -1082,18 +1183,28 @@ namespace DataConnection.REST.Implementations
var entityId = kvp.Key; var entityId = kvp.Key;
var entityData = kvp.Value; var entityData = kvp.Value;
// Normalizza i valori numerici per evitare problemi con virgole decimali
var normalizedData = NormalizeNumericValues(entityData);
var subrequest = new SalesforceCompositeSubRequest var subrequest = new SalesforceCompositeSubRequest
{ {
Method = "PATCH", Method = "PATCH",
Url = $"/services/data/v60.0/sobjects/{entityName}/{entityId}", Url = $"/services/data/v60.0/sobjects/{entityName}/{entityId}",
ReferenceId = $"update_{startIndex + index}", ReferenceId = $"update_{startIndex + index}",
Body = entityData Body = normalizedData
}; };
compositeRequest.CompositeRequest.Add(subrequest); compositeRequest.CompositeRequest.Add(subrequest);
index++; index++;
} }
var response = await _httpClient.PostAsJsonAsync(compositeUri, compositeRequest, cancellationToken); // Usa StringContent con configurazione JSON specifica per Salesforce
var jsonContent = new StringContent(
JsonSerializer.Serialize(compositeRequest, SalesforceJsonOptions),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await _httpClient.PostAsync(compositeUri, jsonContent, cancellationToken);
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
@@ -1112,7 +1223,7 @@ namespace DataConnection.REST.Implementations
} }
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
var compositeResponse = JsonSerializer.Deserialize<SalesforceCompositeResponse>(responseContent); var compositeResponse = JsonSerializer.Deserialize<SalesforceCompositeResponse>(responseContent, SalesforceJsonOptions);
var results = new List<CompositeOperationResult>(); var results = new List<CompositeOperationResult>();
+6 -1
View File
@@ -1627,7 +1627,12 @@ public partial class DataCoupler : ComponentBase
case "edm.decimal": case "edm.decimal":
case "edm.double": case "edm.double":
if (decimal.TryParse(value.ToString(), out decimal decVal)) // Usa InvariantCulture per garantire che i decimali usino il punto come separatore
if (decimal.TryParse(value.ToString(), System.Globalization.NumberStyles.Number,
System.Globalization.CultureInfo.InvariantCulture, out decimal decVal))
return decVal;
// Fallback: prova con la cultura corrente nel caso il valore usi la virgola
if (decimal.TryParse(value.ToString(), out decVal))
return decVal; return decVal;
break; break;
Binary file not shown.