1

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.

AbhiAbzs
  • 134
  • 2
  • 12
  • 2
    That isn't valid in JSON or Javascript. If you are able to tell the other end to encode that stuff properly, then do that, otherwise you're going to have to decode it all manually. – DavidG Sep 07 '21 at 10:17
  • 1
    I know & that would have been great, but that is what apple is returning after performing in app purchase & I can't tell the other end to properly encode as the api would have been getting used by others. – AbhiAbzs Sep 07 '21 at 10:21
  • 1
    It's not valid JS, it will give you a `Uncaught SyntaxError: Invalid left-hand side in assignment` message – DavidG Sep 07 '21 at 10:30
  • 1
    yup true, just now checked it in console, I guess manually parsing it is the only wo to go forwards. Thanks – AbhiAbzs Sep 07 '21 at 10:33
  • 1
    I guess you could `string.Replace(';', ',')` & `string.Replace('=', ':')` but this would of course be horribly hacky and I'd be afraid to deploy something like that to production, unless you can be 100% sure that those characters will never appear inside the keys or values. The alternative of developing your own parser is equally hacky, though. So I'm really not sure about this one. – AsPas Sep 07 '21 at 11:41

4 Answers4

2

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; }
}
smoksnes
  • 10,509
  • 4
  • 49
  • 74
  • 1
    This will break when any of the values contain a semicolon or the space-equals-space sequence. It's pretty fragile. – DavidG Sep 07 '21 at 11:26
  • 1
    @DavidG Agreed. That's why I explicitly stated that it's not pretty and it will work on THIS example. – smoksnes Sep 07 '21 at 11:27
  • 1
    Ugly code should still work robustly, this does not. – DavidG Sep 07 '21 at 11:32
  • 1
    @DavidG, You're obviously right. But we're trying to work with the material at hand. I made some minor improvements which make it _less_ fragile. – smoksnes Sep 07 '21 at 11:36
  • 1
    Thanks, but its error prone, the above would replace `=` in the base64 text also & there would be a trailing `,` at the last line which is not valid for JSON – AbhiAbzs Sep 07 '21 at 11:40
  • 1
    @smoksnes, now it will most probably work, Thanks – AbhiAbzs Sep 07 '21 at 11:43
  • 1
    @AbhiAbzs Well yes and no. Space is not a valid character in a base64 so that won't occur. Nor is `;` a valid character för base64. But I agree that it's fragile. – smoksnes Sep 07 '21 at 11:43
  • 1
    @smoksnes I was talking about replacing `;` with `,` which would cause issue as you can see above that even the last line have `;` in it which would get replaced by `,` & have a trailing `,` in the last, which is not valid json so that also needs to be removed – AbhiAbzs Sep 07 '21 at 11:57
  • @AbhiAbzs- That's why the serializer is configured with `AllowTrailingCommas`, thus making more replacements uneccessary. – smoksnes Sep 08 '21 at 05:44
1

It's a bit embarrassing. I saw the problem as the opposite before. Try this, I think this can meet the needs:

Parse JavaScript string

static void Main(string[] args)
{
    var jsStr = @"{
                    ""signature"" = ""some long base64 string"";
                    ""purchase-info"" = ""ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDAwLTAxLTAxIDAxOjAxOjAxIEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICJzb21lVUlEIjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiU29tZVRJRCI7CgkiYnZycyIgPSAiMy42IjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiU29tZVRJRCI7CgkicXVhbnRpdHkiID0gIjEiOwoJImluLWFwcC1vd25lcnNoaXAtdHlwZSIgPSAiUFVSQ0hBU0VEIjsKCSJvcmlnaW5hbC1wdXJjaGFzZS1kYXRlLW1zIiA9ICJzb21lIG51bWVyaWMgdGltZSBzdGFtcCBpbiBtcyI7CgkidW5pcXVlLXZlbmRvci1pZGVudGlmaWVyIiA9ICJzb21lIFVJRCI7CgkicHJvZHVjdC1pZCIgPSAiMSI7CgkiaXRlbS1pZCIgPSAic29tZSBVbmlxdWUgSXRlbSBJRCI7CgkidmVyc2lvbi1leHRlcm5hbC1pZGVudGlmaWVyIiA9ICIwIjsKCSJpcy1pbi1pbnRyby1vZmZlci1wZXJpb2QiID0gImZhbHNlIjsKCSJwdXJjaGFzZS1kYXRlLW1zIiA9ICJzb21lIG51bWVyaWMgdGltZSBzdGFtcCBpbiBtcyI7CgkicHVyY2hhc2UtZGF0ZSIgPSAiMjAwMC0wMS0wMSAwMTowMTowMSBFdGMvR01UIjsKCSJpcy10cmlhbC1wZXJpb2QiID0gImZhbHNlIjsKCSJvcmlnaW5hbC1wdXJjaGFzZS1kYXRlIiA9ICIyMDAwLTAxLTAxIDAxOjAxOjAxIEV0Yy9HTVQiOwoJImJpZCIgPSAiaXBhIGFwcCBidW5kbGUgbmFtZSI7CgkicHVyY2hhc2UtZGF0ZS1wc3QiID0gIjIwMDAtMDEtMDEgMDE6MDE6MDEgQW1lcmljYS9Mb3NfQW5nZWxlcyI7Cn0="";
                    ""environment"" = ""Sandbox"";
                    ""pod"" = ""100"";
                    ""signing-status"" = ""0"";
                }";

    var model = GetModelFromJsStr<PurchaseToken>(jsStr);
}

public class PurchaseToken
{
    [JsonPropertyName("signature")]
    public string Signature { get; set; }
    [JsonPropertyName("purchase-info")]
    public string PurchaseInfo { get; set; }
    [JsonPropertyName("environment")]
    public string Environment { get; set; }
    [JsonPropertyName("pod")]
    public int Pod { get; set; }
    [JsonPropertyName("signing-status")]
    public int SigningStatus { get; set; }
}

public static T GetModelFromJsStr<T>(string jsStr) where T : new()
{
    var model = new T();
    var rows = jsStr.Split("\n").Select(item => item.Trim()).ToList();
    foreach (var prop in typeof(T).GetProperties())
    {
        var jsonProperty = prop.GetCustomAttribute<JsonPropertyNameAttribute>();

        if (jsonProperty != null)
        {
            var matchRow = rows.FirstOrDefault(row => row.StartsWith(@$"""{jsonProperty.Name}"" ="));
            if (matchRow != null)
            {
                var pattern = @$"^""{jsonProperty.Name}"" = ""(.*)"";$";
                var match = Regex.Match(matchRow, pattern);
                if (match?.Groups?.Count > 1)
                {
                    // convertible types can be extended
                    if (prop.PropertyType == typeof(int))
                    {
                        int.TryParse(match.Groups[1].Value, out var propValue);
                        prop.SetValue(model, propValue);
                    }
                    else
                    {
                        prop.SetValue(model, match.Groups[1].Value);
                    }
                }
            }
        }
    };

    return model;
}
Hanabi
  • 577
  • 4
  • 9
1

I would use Pidgin to parse something like this.

Here's some code that parses your data into a Dictionary<string, string> and prints out the results:

using System;
using System.Collections.Generic;
using System.Linq;
using Pidgin;
using static Pidgin.Parser;

namespace NotQuiteJsonParsing
{
    class Program
    {
        const string PurchaseInfo = @"{
            ""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"";
        }";

        private static string IEnumerableCharToString(IEnumerable<char> value)
        {
            // Feel free to swap this with an alternative answer to https://stackoverflow.com/questions/8108313
            return new string(value.ToArray());
        }

        // Parsers can be slow to build, so minimise the
        // number of times this setup method is called.  See
        // https://github.com/benjamin-hodgson/Pidgin#speed-tips
        private static Parser<char, Dictionary<string, string>> BuildBlockParser()
        {
            var stringParser = Char('"').Then(AnyCharExcept('"').AtLeastOnceUntil(Char('"')));

            var lineParser = Map(
                (key, value) => new KeyValuePair<string, string>(IEnumerableCharToString(key), IEnumerableCharToString(value)),
                stringParser.Before(SkipWhitespaces).Before(Char('=')).Before(SkipWhitespaces),
                stringParser.Before(SkipWhitespaces).Before(Char(';')).Before(SkipWhitespaces));

            var linesParser = lineParser.AtLeastOnce().Map(pairs => new Dictionary<string, string>(pairs));

            var blockParser = SkipWhitespaces.Then(Char('{')).Then(SkipWhitespaces).Then(linesParser).Before(Char('}'));
            return blockParser;
        }

        static void Main()
        {
            var blockParser = BuildBlockParser();
            var result = blockParser.ParseOrThrow(PurchaseInfo);
            foreach (var (key, value) in result)
            {
                Console.WriteLine($"{key} = {value}");
            }
        }
    }
}

This parser has the following limitations:

  • Values in key-value pairs are always strings, not numbers, booleans or anything else.
  • There is no support for escaped " characters in keys or values. I don't know how they would be escaped.
  • There is no support for escape sequences such as \r, \n, \t or \u0123.
Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
0

Was able to handle this by with following code using Newtonsoft.Json.JsonConverter:

Create models for the data

public class InAppPurchaseToken
{
    public string Signature { get; set; }
    [JsonProperty("purchase-info")]
    public string PurchaseInfo { get; set; }
    public string Environment { get; set; }
    public string Pod { get; set; }
    public string SigningStatus { get; set; }
}


public class InAppPurchaseInfo
{
    [JsonProperty("original-purchase-date-pst")]
    public string originalpurchasedatepst { get; set; }
    [JsonProperty("unique-identifier")]
    public string uniqueidentifier { get; set; }
    [JsonProperty("original-transaction-id")]
    public string originaltransactionid { get; set; }
    public string bvrs { get; set; }
    [JsonProperty("transaction-id")]
    public string transactionid { get; set; }
    public string quantity { get; set; }
    [JsonProperty("in-app-ownership-type")]
    public string inappownershiptype { get; set; }
    [JsonProperty("original-purchase-date-ms")]
    public string originalpurchasedatems { get; set; }
    [JsonProperty("unique-vendor-identifier")]
    public string uniquevendoridentifier { get; set; }
    [JsonProperty("product-id")]
    public string productid { get; set; }
    [JsonProperty("item-id")]
    public string itemid { get; set; }
    [JsonProperty("version-external-identifier")]
    public string versionexternalidentifier { get; set; }
    [JsonProperty("is-in-intro-offer-period")]
    public string isinintroofferperiod { get; set; }
    [JsonProperty("purchase-date-ms")]
    public string purchasedatems { get; set; }
    [JsonProperty("purchase-date")]
    public string purchasedate { get; set; }
    [JsonProperty("is-trial-period")]
    public string istrialperiod { get; set; }
    [JsonProperty("original-purchase-date")]
    public string originalpurchasedate { get; set; }
    public string bid { get; set; }
    [JsonProperty("purchase-date-pst")]
    public string purchasedatepst { get; set; }
}

And the handle the parsing using following code (assuming you have deserialized the initial json as inAppPayData C# object):

public string GetJsonFromBase64JsStr(string base64JsStr)
{
    if (string.IsNullOrWhiteSpace(base64JsStr))
        return "";

    var decodedStr2Parse = Encoding.UTF8.GetString(Convert.FromBase64String(base64JsStr));

    //var ConvertedJsonStrComplete = decodedStr2Parse.Replace("\" = \"", "\":\"").Replace(";\n", ",").Replace(",}", "}");  //more error prone
    var ConvertedJsonStrPartial = decodedStr2Parse.Replace("\" = \"", "\":\"").Replace(";\n", ",");
    var ConvertedJsonStrComplete = ConvertedJsonStrPartial.Remove(ConvertedJsonStrPartial.LastIndexOf(",}"), 1);

     return ConvertedJsonStrComplete;
}

//call the above function with base64JsString
var purchaseJsonStr = GetJsonFromBase64JsStr(inAppPayData.PurchaseToken);

var purchaseTokenObj = JsonConvert.DeserializeObject<InAppPurchaseToken>(purchaseJsonStr);


var purchaseInfoJsonStr = GetJsonFromBase64JsStr(purchaseTokenObj.PurchaseInfo);

var purchaseInfoObj = JsonConvert.DeserializeObject<InAppPurchaseInfo>(purchaseInfoJsonStr);


AbhiAbzs
  • 134
  • 2
  • 12