-3

I am trying to consume a web service that claims to return JSON, but actually always returns JSONP. I don't see a way to change that service's behavior.

I would like to use NewtonSoft Json.Net to parse the result. I have declared a class, let's call it MyType that I want to deserialize the inner JSON result into.

JSONP:

parseResponse({
"total" : "13,769",
"lower" : "1",
"upper" : "20"})

As you can see this is not correct JSON as it has parseResponse( prefix and ) suffix. While this example is very simple, the actual response can be quite long, on the order of 100Ks.

MyType:

public class MyType
{
    public Decimal total;
    public int lower;
    public int upper;
}

After I get my web service response into a stream and JsonTextReader I try to deserialize like this:

(MyType)serializer.Deserialize(jsonTextReader, typeof(MyType));

Of course I get null for a result because there is that pesky parseResponse with round brackets.

I've taken a look at this question which unfortunately does not help. I'm actually using a JsonTextReader to feed in the JSON, rather than a string (and prefer so to avoid the performance hit of creating huge a string). Even if I'd use the suggestion from that question, it looks dangerous as it uses a global replace. If there is no good way to use a stream, an answer with safe parsing of strings would be okay.

Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144
user6694745
  • 390
  • 1
  • 13
  • 1
    So you can detect the error from the return value being null. Are you asking what to do if the return value is found to be null? – Makketronix Jan 26 '18 at 23:27
  • 1
    @Makketronix No. I am asking how to handle such malformed JSON. – user6694745 Jan 26 '18 at 23:55
  • 1
    Is the `parseResponse(` prefix string fixed? – dbc Jan 27 '18 at 02:36
  • 1
    @dbc Yes, As far as I could tell (a lot of testing) it is. The whole response has a form similar to a function: 'parseResponse(JSON)'. And I want to handle only the JSON part. – user6694745 Jan 27 '18 at 07:35
  • 5
    Why should I? The question *as asked* has no indication that reading response as a string and using `.Replace` does not work for your case. So everyone else who sees this question will not understand how it is different from tons of other "parse JSONP" questions and whether this one matches better to their case. Again - you demonstrated strong attachment to original version of your post so I doubt anyone else would be willing to edit it in your comments to clarify what exactly you are looking and why reading as string is not the answer. – Alexei Levenkov Jan 27 '18 at 21:38
  • @AlexeiLevenkov It is clear to everybody that has a bit of clue about json.Net. It is obvious ffrom (MyType)serializer.Deserialize(jsonTextReader, typeof(MyType)); that using String is not an option. Besides perforamce. You really should not mess with questions on topics you have no clue about. – user6694745 Jan 27 '18 at 23:26
  • 10
    @user6694745 it's not malformed it's just JSONP not JSON, stop criticising those who are trying to help you. – user692942 Jan 29 '18 at 00:00
  • @user6694745 Might find this helpful - [Creating a JSONP Formatter for ASP.NET Web API](https://weblog.west-wind.com/posts/2012/Apr/02/Creating-a-JSONP-Formatter-for-ASPNET-Web-API). – user692942 Jan 29 '18 at 00:17
  • @Lankymart the link is for constructing JSONP, not reading as far as I can tell. – Alexei Levenkov Jan 29 '18 at 01:10
  • 1
    @AlexeiLevenkov it wasn't a solution it just explains what JSONP is as the OP seems to think its *"malformed JSON"*. – user692942 Jan 29 '18 at 01:16
  • 5
    @user6694745 another option if you feel this question no longer reflects your needs and style is to request to disassociate from your account - if you want to do so flag post for moderator attention and explain that. – Alexei Levenkov Jan 29 '18 at 01:41

2 Answers2

21

If I interpret your question as follows:

I am trying to deserialize some JSON from a Stream. The "JSON" is actually in JSONP format and so contains some prefix and postfix text I would like to ignore. How can I skip the prefix and postfix text while still reading and deserializing directly from stream rather than loading the entire stream into a string?

Then you can deserialize your JSON from a JSONP stream using the following extension method:

public static class JsonExtensions
{
    public static T DeserializeEmbeddedJsonP<T>(Stream stream)
    {
        using (var textReader = new StreamReader(stream))
            return DeserializeEmbeddedJsonP<T>(textReader);
    }

    public static T DeserializeEmbeddedJsonP<T>(TextReader textReader)
    {
        using (var jsonReader = new JsonTextReader(textReader.SkipPast('(')))
        {
            var settings = new JsonSerializerSettings
            {
                CheckAdditionalContent = false,
            };
            return JsonSerializer.CreateDefault(settings).Deserialize<T>(jsonReader);
        }
    }
}

public static class TextReaderExtensions
{
    public static TTextReader SkipPast<TTextReader>(this TTextReader reader, char ch) where TTextReader : TextReader
    {
        while (true)
        {
            var c = reader.Read();
            if (c == -1 || c == ch)
                return reader;
        }
    }
}

Notes:

  • Prior to constructing the JsonTextReader I construct a StreamReader and skip past the first '(' character in the stream. This positions the StreamReader at the beginning of the actual JSON.

  • Before deserialization I set JsonSerializerSettings.CheckAdditionalContent = false to tell the serializer to ignore any characters after the end of the JSON content. Oddly enough it is necessary to do this explicitly despite the fact that the default value seems to be false already, since the underlying field is nullable.

  • The same code can be used to deserialize embedded JSONP from a string by passing a StringReader to DeserializeEmbeddedJsonP<T>(TextReader reader);. Doing so avoids the need to create a new string by trimming the prefix and postfix text and so may improve performance and memory use even for smaller strings.

Sample working .Net fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • I have come to similar solution myself although a bit more clumsy. I used while loop in stream to "skip past" '('. CheckAdditionalContent option was another thing that took me some time. Your answer would save me quite some time. Your answer sort of confirms my train of thought. And a SkupPast() method did add a measure of elegance. Besides that, your answer is short, structured, with a demo. In a word: good. All the reason to accept and upvote it. Thanks. – user6694745 Feb 02 '18 at 09:11
6

It looks like it's returning JSONP. Kind of weird that a webservice would do that by default, without you including "?callback". In any case, if that's just the way it is, you can easily use a RegEx to just strip off the method call:

var x = WebServiceCall();
x = Regex.Replace(x, @"^.+?\(|\)$", "");
aquinas
  • 23,318
  • 5
  • 58
  • 81
  • I am not operating directly with strings. Regex does not word on streams. And although I have not checked your regualr expression does deal with 'parseResponse'. – user6694745 Jan 26 '18 at 23:46
  • OK, convert to a string first? What's the problem? And yes, the regex works with ANY JSONP. – aquinas Jan 26 '18 at 23:50
  • Pay attention to question, Method Deserialize does not take string arguments. – user6694745 Jan 26 '18 at 23:54
  • 6
    Yes I get that. :) Why can't you call `JsonConvert.DeserializeObject(someString)` which DOES take a string? If you want to take your stream directly from your response and put it into a JSON reader, well, it's not going to work. You HAVE to process it first. https://www.newtonsoft.com/json/help/html/DeserializeObject.htm – aquinas Jan 27 '18 at 00:01
  • 4
    @user6694745 - "Method Deserialize does not take string arguments" - `JsonSerializer.Deserialize` has an overload that takes a `TextReader`, which can be a `StringReader`. – Joe Jan 28 '18 at 18:08