0

I am having an issue deserializing a JSON string from an HTTPS web response. Whenever I attempt to do so, it returns the following error:

Additional information: Invalid JSON primitive: .

(Including the period)

Now, because I know for a fact the server is sending a valid JSON string (Fiddler says so) I believe that the string may be encrypted through HTTPS and that is messing with my deserialization. How can I fix this error and correctly deserialize the json string:

JSON

I attempted using both the .NET framework and Newtsoft's JSON.NET as seen in the code below.

        StringBuilder login = new StringBuilder();
        login.Append("json=%7B%22username%22%3A%22csharpautomaton%22%2C%22password%22%3A%22[redacted password]%22%2C%22remember%22%3A0%7D");
        byte[] logBytes = encoding.GetBytes(login.ToString());

        HttpWebRequest requirejs = (HttpWebRequest)WebRequest.Create(new Uri("https://www.textnow.com/api/sessions"));

        //requirejs.PreAuthenticate = true;
        requirejs.KeepAlive = true;
        requirejs.Method = "POST";
        requirejs.Accept = "Accept: application/json, text/javascript, */*; q=0.01";
        requirejs.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
        requirejs.Headers.Add("Accept-Language", "en-US,en;q=0.8");
        requirejs.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13";
        requirejs.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";

        Stream sw = requirejs.GetRequestStream();
        sw.Write(logBytes, 0, logBytes.Length);
        sw.Close();
        response = (HttpWebResponse)requirejs.GetResponse();
        Stream stream = response.GetResponseStream();
        StreamReader sr = new StreamReader(stream,true);

        string line = sr.ReadLine();
        //the one I tried second
        TheObject id = JsonConvert.DeserializeObject<TheObject>(line);
        MessageBox.Show(id.ToString());

        //the one I tried first v
        System.Web.Script.Serialization.JavaScriptSerializer deserialize = new System.Web.Script.Serialization.JavaScriptSerializer();
        TheObject jsonObject = deserialize.Deserialize<TheObject>(sr.ReadToEnd());

//the object I used with just .NET
public class TheObject
{
    string id { get; set; } 
    string username { get; set; }
}
//the object I used with JSON.NET
public class TheObject
{
    [JsonProperty("id")]
    public string id { get; set; }

    [JsonProperty("username")]
    public string username { get; set; }
}
Chris Altig
  • 680
  • 3
  • 8
  • 22
  • https won't interfere with deserialization -- if the request is successful, you'll get the decrypted stream back. Do you have documentation on textnow's API? Calling https://www.textnow.com/api/sessions without authentication returns a (useless) error. Would like to be able to try this with test credentials. I also take it you didn't try with [`System.Web.Helpers.Json`](http://stackoverflow.com/questions/20437279/getting-json-data-from-a-response-stream-and-reading-it-as-a-string?noredirect=1#comment30531426_20437279)? – brandonscript Dec 07 '13 at 07:35
  • In your second try you only parse one line (`ReadLine`). Are you sure you get your complete json string in one line? Also, what is the content of `login`? Is it valid x-www-form-urlencoded content? – user3038092 Dec 07 '13 at 08:06
  • @user3038092 I added the code for login, and it should be valid. . – Chris Altig Dec 07 '13 at 18:39
  • @r3mus I do not have documentation on TextNow's API, I do not believe they even have one available to the public. No, I did not try it with System.Web.Helpers.Json, do you think that would assist in my issue? – Chris Altig Dec 07 '13 at 18:39
  • Honestly don't know in this case, but might be worth a shot. .NET never adopted JSON very well (hence the various implementations), best to try them all. I will have a look and see what I can come up with. – brandonscript Dec 07 '13 at 18:50
  • I am using WinForms, is this why I cannot locate the System.Web.Helpers.Json ? – Chris Altig Dec 07 '13 at 19:07
  • http://stackoverflow.com/questions/8037895/where-can-i-find-system-web-helpers-system-web-webpages-and-system-web-razor – brandonscript Dec 07 '13 at 19:20
  • Also -- I think I see the problem. TN is requiring the `username` parameter and `password` parameter to be passed as x-www-form-urlencoded content (form value pairs). You can't pass it the JSON string like that, you need to decode the JSON string into a usable object and then pass those parameters. – brandonscript Dec 07 '13 at 19:23

2 Answers2

1

Turns out this didn't work either, but was part of the solution:

We determined that TextNow actually requires an escaped JSON string to be passed to the json= key, like so:

json={\"username\":\"csharpautomaton\",\"password\":\"[redacted password]\",\"remember\":0}

More information, though this didn't work:

TextNow is requiring the username parameter and password parameter to be passed as x-www-form-urlencoded content (form value pairs).

You can't pass it the full url-encoded JSON string like that, you need to decode the JSON string into a usable object (you're already doing this with jsonObject) and then pass those parameters.

In the end, you need to have the equivalent of this:

StringBuilder login = new StringBuilder();
login.Append("username=csharpautomation&password=xxxxx");
byte[] logBytes = encoding.GetBytes(login.ToString());

Since you're already implementing the jsonObject in your class(es) (albeit with id/username instead of username/password?) it shouldn't be much of a challenge to rebuild the parameter string once you've decoded the JSON object.

brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • 1
    Since they don't have any docs on their API, I can't be completely certain, but in all my years working with APIs, I've never seen one that expects a JSON payload *as* a urlencoded key value pair. It's either raw JSON that you post to the server, or key:value pairs. Go grab [POSTMAN](http://www.getpostman.com) and `POST` the username/password keypairs. You'll see that if you pass json=xxxxx, it returns `PARAMETER_MISSING` whereas if you pass username=&password= it returns `SIGNATURE_INVALID` (past that, I can't tell you why it's not happy). – brandonscript Dec 07 '13 at 20:55
  • So I fixed the login bytes. But I still get the invalid json primitive? I am getting a good response, but when I look through it, I see no periods within it, what could be the problem? – Chris Altig Dec 07 '13 at 20:56
  • Anyway, I figured out how to fix the login, I am now sending it as: "json={\"username\":\"csharpautomaton\",\"password\":\"[redacted password]\",\"remember\":0}" – Chris Altig Dec 07 '13 at 20:57
  • That's crazy! Goes against every API best practice. Great that you sorted it out though. – brandonscript Dec 07 '13 at 20:59
  • Can you explain more about that last paragraph of the answer? I am still a little confused at what you're saying. – Chris Altig Dec 07 '13 at 21:33
  • Normal RESTful APIs will spit out JSON like `{"this":"that"}`. In your case, you had to pass *that string* escaped with a backslash. Normally though, you would decode `{"this":"that"}` into a usable array of data, exactly like you did with `TheObject`, and when you need to `POST` data, you would pass an array back transformed into either key=value pairs, or serialized into JSON and passed as RAW POST data. – brandonscript Dec 07 '13 at 21:39
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/42723/discussion-between-user2396111-and-r3mus) – Chris Altig Dec 07 '13 at 23:47
1

As it turns out, the server was sending a compressed response. So, all I needed to do in the end was remove "Accept-Encoding","gzip,deflate,sdch", however I could have manually decompressed it as well, but this was an easier option. @r3mus thanks for all your help, you were wonderful, and frankly, I was amazed at the amount of time you spent helping me, I am very thankful!

Chris Altig
  • 680
  • 3
  • 8
  • 22