Search code examples
javascriptc#jsonjson.net

Deserialize javascript string rather then JSON using C#


Actually I've implmented ios in-app purchase & upon success, I receive json response. The json response contains PurchaseToken, which is basically a base64 encoded javascript string.

{
    "Id": "1000000872920320",
    "TransactionDateUtc": "2021-09-06T14:42:17Z",
    "ProductId": "1",
    "AutoRenewing": false,
    "PurchaseToken": "ewoJInNpZ25hdHVyZSIgPSAic29tZSBsb25nIGJhc2U2NCBzdHJpbmciOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1EQXdMVEF4TFRBeElEQXhPakF4T2pBeElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5WdWFYRjFaUzFwWkdWdWRHbG1hV1Z5SWlBOUlDSnpiMjFsVlVsRUlqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpVTI5dFpWUkpSQ0k3Q2draVluWnljeUlnUFNBaU15NDJJanNLQ1NKMGNtRnVjMkZqZEdsdmJpMXBaQ0lnUFNBaVUyOXRaVlJKUkNJN0Nna2ljWFZoYm5ScGRIa2lJRDBnSWpFaU93b0pJbWx1TFdGd2NDMXZkMjVsY25Ob2FYQXRkSGx3WlNJZ1BTQWlVRlZTUTBoQlUwVkVJanNLQ1NKdmNtbG5hVzVoYkMxd2RYSmphR0Z6WlMxa1lYUmxMVzF6SWlBOUlDSnpiMjFsSUc1MWJXVnlhV01nZEdsdFpTQnpkR0Z0Y0NCcGJpQnRjeUk3Q2draWRXNXBjWFZsTFhabGJtUnZjaTFwWkdWdWRHbG1hV1Z5SWlBOUlDSnpiMjFsSUZWSlJDSTdDZ2tpY0hKdlpIVmpkQzFwWkNJZ1BTQWlNU0k3Q2draWFYUmxiUzFwWkNJZ1BTQWljMjl0WlNCVmJtbHhkV1VnU1hSbGJTQkpSQ0k3Q2draWRtVnljMmx2YmkxbGVIUmxjbTVoYkMxcFpHVnVkR2xtYVdWeUlpQTlJQ0l3SWpzS0NTSnBjeTFwYmkxcGJuUnlieTF2Wm1abGNpMXdaWEpwYjJRaUlEMGdJbVpoYkhObElqc0tDU0p3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNKemIyMWxJRzUxYldWeWFXTWdkR2x0WlNCemRHRnRjQ0JwYmlCdGN5STdDZ2tpY0hWeVkyaGhjMlV0WkdGMFpTSWdQU0FpTWpBd01DMHdNUzB3TVNBd01Ub3dNVG93TVNCRmRHTXZSMDFVSWpzS0NTSnBjeTEwY21saGJDMXdaWEpwYjJRaUlEMGdJbVpoYkhObElqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbElpQTlJQ0l5TURBd0xUQXhMVEF4SURBeE9qQXhPakF4SUVWMFl5OUhUVlFpT3dvSkltSnBaQ0lnUFNBaWFYQmhJR0Z3Y0NCaWRXNWtiR1VnYm1GdFpTSTdDZ2tpY0hWeVkyaGhjMlV0WkdGMFpTMXdjM1FpSUQwZ0lqSXdNREF0TURFdE1ERWdNREU2TURFNk1ERWdRVzFsY21sallTOU1iM05mUVc1blpXeGxjeUk3Q24wPSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0=",
    "State": 0,
    "ConsumptionState": 0,
    "IsAcknowledged": false,
    "Payload": null
}

I deserialize the above object & decode the base64 purchaseToken, but the decoded string is javascript string rather then json (look at the = & ;).

{
    "signature" = "some long base64 string";
    "purchase-info" = "ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDAwLTAxLTAxIDAxOjAxOjAxIEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICJzb21lVUlEIjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiU29tZVRJRCI7CgkiYnZycyIgPSAiMy42IjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiU29tZVRJRCI7CgkicXVhbnRpdHkiID0gIjEiOwoJImluLWFwcC1vd25lcnNoaXAtdHlwZSIgPSAiUFVSQ0hBU0VEIjsKCSJvcmlnaW5hbC1wdXJjaGFzZS1kYXRlLW1zIiA9ICJzb21lIG51bWVyaWMgdGltZSBzdGFtcCBpbiBtcyI7CgkidW5pcXVlLXZlbmRvci1pZGVudGlmaWVyIiA9ICJzb21lIFVJRCI7CgkicHJvZHVjdC1pZCIgPSAiMSI7CgkiaXRlbS1pZCIgPSAic29tZSBVbmlxdWUgSXRlbSBJRCI7CgkidmVyc2lvbi1leHRlcm5hbC1pZGVudGlmaWVyIiA9ICIwIjsKCSJpcy1pbi1pbnRyby1vZmZlci1wZXJpb2QiID0gImZhbHNlIjsKCSJwdXJjaGFzZS1kYXRlLW1zIiA9ICJzb21lIG51bWVyaWMgdGltZSBzdGFtcCBpbiBtcyI7CgkicHVyY2hhc2UtZGF0ZSIgPSAiMjAwMC0wMS0wMSAwMTowMTowMSBFdGMvR01UIjsKCSJpcy10cmlhbC1wZXJpb2QiID0gImZhbHNlIjsKCSJvcmlnaW5hbC1wdXJjaGFzZS1kYXRlIiA9ICIyMDAwLTAxLTAxIDAxOjAxOjAxIEV0Yy9HTVQiOwoJImJpZCIgPSAiaXBhIGFwcCBidW5kbGUgbmFtZSI7CgkicHVyY2hhc2UtZGF0ZS1wc3QiID0gIjIwMDAtMDEtMDEgMDE6MDE6MDEgQW1lcmljYS9Mb3NfQW5nZWxlcyI7Cn0=";
    "environment" = "Sandbox";
    "pod" = "100";
    "signing-status" = "0";
}

And the purchase info:

{
    "original-purchase-date-pst" = "2000-01-01 01:01:01 America/Los_Angeles";
    "unique-identifier" = "someUID";
    "original-transaction-id" = "SomeTID";
    "bvrs" = "3.6";
    "transaction-id" = "SomeTID";
    "quantity" = "1";
    "in-app-ownership-type" = "PURCHASED";
    "original-purchase-date-ms" = "some numeric time stamp in ms";
    "unique-vendor-identifier" = "some UID";
    "product-id" = "1";
    "item-id" = "some Unique Item ID";
    "version-external-identifier" = "0";
    "is-in-intro-offer-period" = "false";
    "purchase-date-ms" = "some numeric time stamp in ms";
    "purchase-date" = "2000-01-01 01:01:01 Etc/GMT";
    "is-trial-period" = "false";
    "original-purchase-date" = "2000-01-01 01:01:01 Etc/GMT";
    "bid" = "ipa app bundle name";
    "purchase-date-pst" = "2000-01-01 01:01:01 America/Los_Angeles";
}

I'm facing this issue with deserializing of the javascript string into a C# object. Any proper way to handle this deserialization? Newtonsoft.Json is not able to handle it & I have looked its docs again & again to find any suitable methods but no success.

Is there any way other then to replace characters & make its json like?

Any help would be highly appreciated.


Solution

  • As others already mentioned, it's not JSON. But based on the example you provided you should be able to transform it to a readable JSON. It's not the most pretty solution, but it seems to work. It have some draw-backs:

    • It will only work when " = " is the separator.

    • It will only work when each field is separated by ; followed by line-break (windows-style).

        private string MakeReadableJson(string base64Input)
        {
            var data = System.Convert.FromBase64String(base64Input);
            string readableString = Encoding.UTF8.GetString(data);
            var json = readableString.Replace("\" = \"", "\":\"").Replace(";\n", ",");
            return json;
        }
      

    I use System.Text.JsonSerializer. Use it like so:

                string json = @"{
                       ""Id"": ""1000000872920320"",
                       ""TransactionDateUtc"": ""2021-09-06T14:42:17Z"",
                       ""ProductId"": ""1"",
                       ""AutoRenewing"": false,
                       ""PurchaseToken"": ""ewoJInNpZ25hdHVyZSIgPSAic29tZSBsb25nIGJhc2U2NCBzdHJpbmciOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1EQXdMVEF4TFRBeElEQXhPakF4T2pBeElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5WdWFYRjFaUzFwWkdWdWRHbG1hV1Z5SWlBOUlDSnpiMjFsVlVsRUlqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpVTI5dFpWUkpSQ0k3Q2draVluWnljeUlnUFNBaU15NDJJanNLQ1NKMGNtRnVjMkZqZEdsdmJpMXBaQ0lnUFNBaVUyOXRaVlJKUkNJN0Nna2ljWFZoYm5ScGRIa2lJRDBnSWpFaU93b0pJbWx1TFdGd2NDMXZkMjVsY25Ob2FYQXRkSGx3WlNJZ1BTQWlVRlZTUTBoQlUwVkVJanNLQ1NKdmNtbG5hVzVoYkMxd2RYSmphR0Z6WlMxa1lYUmxMVzF6SWlBOUlDSnpiMjFsSUc1MWJXVnlhV01nZEdsdFpTQnpkR0Z0Y0NCcGJpQnRjeUk3Q2draWRXNXBjWFZsTFhabGJtUnZjaTFwWkdWdWRHbG1hV1Z5SWlBOUlDSnpiMjFsSUZWSlJDSTdDZ2tpY0hKdlpIVmpkQzFwWkNJZ1BTQWlNU0k3Q2draWFYUmxiUzFwWkNJZ1BTQWljMjl0WlNCVmJtbHhkV1VnU1hSbGJTQkpSQ0k3Q2draWRtVnljMmx2YmkxbGVIUmxjbTVoYkMxcFpHVnVkR2xtYVdWeUlpQTlJQ0l3SWpzS0NTSnBjeTFwYmkxcGJuUnlieTF2Wm1abGNpMXdaWEpwYjJRaUlEMGdJbVpoYkhObElqc0tDU0p3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNKemIyMWxJRzUxYldWeWFXTWdkR2x0WlNCemRHRnRjQ0JwYmlCdGN5STdDZ2tpY0hWeVkyaGhjMlV0WkdGMFpTSWdQU0FpTWpBd01DMHdNUzB3TVNBd01Ub3dNVG93TVNCRmRHTXZSMDFVSWpzS0NTSnBjeTEwY21saGJDMXdaWEpwYjJRaUlEMGdJbVpoYkhObElqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbElpQTlJQ0l5TURBd0xUQXhMVEF4SURBeE9qQXhPakF4SUVWMFl5OUhUVlFpT3dvSkltSnBaQ0lnUFNBaWFYQmhJR0Z3Y0NCaWRXNWtiR1VnYm1GdFpTSTdDZ2tpY0hWeVkyaGhjMlV0WkdGMFpTMXdjM1FpSUQwZ0lqSXdNREF0TURFdE1ERWdNREU2TURFNk1ERWdRVzFsY21sallTOU1iM05mUVc1blpXeGxjeUk3Q24wPSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0="",
                       ""State"": 0,
                       ""ConsumptionState"": 0,
                       ""IsAcknowledged"": false,
                       ""Payload"": null
                   }";
    
                var info = JsonSerializer.Deserialize<ResultFromApple>(json);
    
                var purchaseTokenJson = MakeReadableJson(info.PurchaseToken);
                PurchaseToken purchaseToken = JsonSerializer.Deserialize<PurchaseToken>(purchaseTokenJson, new JsonSerializerOptions()
                {
                    AllowTrailingCommas = true
                });
    
                var purchaseInfoJson = MakeReadableJson(purchaseToken.PurchaseInfo);
                PurchaseInfo purchaseInfo = JsonSerializer.Deserialize<PurchaseInfo>(purchaseInfoJson, new JsonSerializerOptions()
                {
                    AllowTrailingCommas = true
                });
    

    And your models:

    public class PurchaseToken
    {
        public string signature { get; set; }
    
        [JsonPropertyName("purchase-info")]
        public string PurchaseInfo { get; set; }
        public string environment { get; set; }
        public string pod { get; set; }
    
        [JsonPropertyName("signing-status")]
        public string SigningStatus { get; set; }
    }
    
    public class ResultFromApple
    {
        public string Id { get; set; }
        public DateTime TransactionDateUtc { get; set; }
        public string ProductId { get; set; }
        public bool AutoRenewing { get; set; }
        public string PurchaseToken { get; set; }
        public int State { get; set; }
        public int ConsumptionState { get; set; }
        public bool IsAcknowledged { get; set; }
        public object Payload { get; set; }
    }
    
    public class PurchaseInfo
    {
        [JsonPropertyName("original-purchase-date-pst")]
        public string OriginalPurchaseDatePst { get; set; }
    
        [JsonPropertyName("unique-identifier")]
        public string UniqueIdentifier { get; set; }
    
        [JsonPropertyName("original-transaction-id")]
        public string OriginalTransactionId { get; set; }
        public string bvrs { get; set; }
    
        [JsonPropertyName("transaction-id")]
        public string TransactionId { get; set; }
        public string quantity { get; set; }
    
        [JsonPropertyName("in-app-ownership-type")]
        public string InAppOwnershipType { get; set; }
    
        [JsonPropertyName("original-purchase-date-ms")]
        public string OriginalPurchaseDateMs { get; set; }
    
        [JsonPropertyName("unique-vendor-identifier")]
        public string UniqueVendorIdentifier { get; set; }
    
        [JsonPropertyName("product-id")]
        public string ProductId { get; set; }
    
        [JsonPropertyName("item-id")]
        public string ItemId { get; set; }
    
        [JsonPropertyName("version-external-identifier")]
        public string VersionExternalIdentifier { get; set; }
    
        [JsonPropertyName("is-in-intro-offer-period")]
        public string IsInIntroOfferPeriod { get; set; }
    
        [JsonPropertyName("purchase-date-ms")]
        public string PurchaseDateMs { get; set; }
    
        [JsonPropertyName("purchase-date")]
        public string PurchaseDate { get; set; }
    
        [JsonPropertyName("is-trial-period")]
        public string IsTrialPeriod { get; set; }
    
        [JsonPropertyName("original-purchase-date")]
        public string OriginalPurchaseDate { get; set; }
        public string bid { get; set; }
    
        [JsonPropertyName("purchase-date-pst")]
        public string PurchaseDatePst { get; set; }
    }