Search code examples
c#json.net.net-6.0

Parsing a json with datetimeoffset from PHP to C# results in wrong data


I have a webhook call from a third party which is a complex JSON object, in this object, there are some datetime fields like this 2023-04-05T14:13:47+0200, so when I parse this JSON with C#, the result is 2023-04-05T14:13:47+02:00, and that causes an issue when calculating the hash code.

This is the JSON:

{
    "message": "OK",
    "code": 200,
    "current_time": "2023-04-05T14:13:50+0200",
    "order": {
                "uuid": "45B32B3C-B01A-4D82-A03B-D5D9709DC92C",
                "created": "2023-04-05T14:13:47+0200",
                "created_from_client_timezone": "2023-04-05T14:13:47+0200",
                "amount": 1000,
                "currency": "978",
                "paid": false, 
                "status": "CREATED",
                "safe": true,
                "refunded": 0,
                "additional": null,
                "service": "REDSYSPSD2",
                "service_uuid": "F9D76AE8-DDBB-4F17-8074-C2DABEE0A919",
                "customer": "test2222",
                "cof_txnid": null,
                "transactions": [],
                "token": "345e77cc5a2616030ef5965d5513e1088adbd087b637da99eefc80f0729c943a26ef5e3a45b454819c62015e3d74f3282ecd2e1ab0b99a34de46d8aa37106a3d",
                "ip": null,
                "urls": { 
                            "payment_card":"http:\/\/ws.paylands.loc\/su\/QY6cnT3MasUB",
                            "bizum":"http:\/\/ws.paylands.loc\/su\/0SWxTAvN0QkJ",
                            "3ds_tokenized":"http:\/\/ws.paylands.loc\/su\/3wEDqxIhjjsD"
                        },
                "reference": null,
                "dynamic_descriptor": "\u00eb\u00f1a,\u00e1ds,\u00f4 \u00f2,",
                "threeds_data": null
           },
    "client": {
                  "uuid":"42B8CF56-A7D7-4D4A-8349-4E27263CB2D5"
              },
    "validation_hash": "414c8190e6b15ac767ff3217e994ef8687368ec8ebe6ab9ad570799d2f746cb7"
}

And this is how the validation_hash is calculated:

var jsonDe = JsonConvert.DeserializeObject<dynamic>(requestBody);

var signatureData = JsonConvert.SerializeObject(new Dictionary<string, object>
{
    { "order", jsonDe.order },
    { "client", jsonDe.client },
});

var signature = CalculateSHA256Hash(signatureData + settings.Signature);

if (signature != dto.ValidationHash)
{
    serviceResult.AddError(0, _localizationRepository.Get("The request is invalid."));
    return serviceResult;
}

static string CalculateSHA256Hash(string input)
{
    using (SHA256 sha256Hash = SHA256.Create())
    {
        byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

        StringBuilder builder = new StringBuilder();

        for (int i = 0; i < bytes.Length; i++)
        {
            builder.Append(bytes[i].ToString("x2"));
        }

        return builder.ToString();
    }
}

I have tested several converters, but none of them could fix the issue of not adding : to the DateTime field.

I modified the fields after the DeserializeObject and removed the last : but that's not something I would prefer, so is there any other solution for this issue?

FYI, this is an example from their support with PHP: https://onlinephp.io/c/4a0c8


Solution

  • If you are using Newtonsoft's Json.NET (the one with JsonConvert) - then simply disable the automatic DateTime handling (also I recommend switching from using dynamic to types exposed by the library for dynamic JSON handling):

    var requestBody = """
    {
        "current_time": "2023-04-05T14:13:50+0200",
        "order": {
                    "uuid": "45B32B3C-B01A-4D82-A03B-D5D9709DC92C",
                    "created": "2023-04-05T14:13:47+0200",
                    "created_from_client_timezone": "2023-04-05T14:13:47+0200",
               }
    }
    """;
    var jsonDe = JsonConvert.DeserializeObject<JToken>(requestBody, new JsonSerializerSettings
    {
        DateParseHandling = DateParseHandling.None
    });
    Console.WriteLine(jsonDe["current_time"].ToString()); //2023-04-05T14:13:50+0200
    Console.WriteLine(jsonDe["order"]["created_from_client_timezone"].ToString()); // 2023-04-05T14:13:47+0200
    
    var signatureData = JsonConvert.SerializeObject(new Dictionary<string, object>
    {
        { "order", jsonDe["order"] },
        { "client", jsonDe["client"] },
    }); // {"order":{"uuid":"45B32B3C-B01A-4D82-A03B-D5D9709DC92C","created":"2023-04-05T14:13:47+0200","created_from_client_timezone":"2023-04-05T14:13:47+0200"},"client":null}
    

    For System.Text.Json (if you decide to switch) - use similar API for example JsonNode (works out of the box for me, no date handling happening):

    var jsonNode = JsonSerializer.Deserialize<JsonNode>(requestBody); // or JsonNode.Parse
    Console.WriteLine(jsonNode["order"]["created_from_client_timezone"].ToString()); // 2023-04-05T14:13:47+0200
    

    P.S.

    Be sure that indentation is also handled in the same way on both sides.