如何反序列化 JSONP 响应(最好使用 JsonTextReader 而不是字符串)?

Posted

技术标签:

【中文标题】如何反序列化 JSONP 响应(最好使用 JsonTextReader 而不是字符串)?【英文标题】:How to deserialize a JSONP response (preferably with JsonTextReader and not a string)? 【发布时间】:2018-07-06 08:39:20 【问题描述】:

我正在尝试使用声称返回 JSON,但实际上总是返回 JSONP 的 Web 服务。我看不到改变该服务行为的方法。

我想使用 NewtonSoft Json.Net 来解析结果。我已经声明了一个类,我们称它为 MyType,我想将内部 JSON 结果反序列化为。

JSONP:

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

如您所见,这不是正确的 JSON,因为它有 parseResponse( 前缀和 ) 后缀。虽然此示例非常简单,但实际响应可能会很长,大约 100Ks。

我的类型:

public class MyType

    public Decimal total;
    public int lower;
    public int upper;

在我将 Web 服务响应放入流和 JsonTextReader 后,我尝试像这样反序列化:

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

当然,我的结果是 null,因为 parseResponse 带有圆括号。

我查看了this question,不幸的是它没有帮助。我实际上使用JsonTextReader 来输入JSON,而不是字符串(并且更喜欢这样做以避免创建巨大的字符串对性能造成影响)。即使我使用该问题的建议,它看起来也很危险,因为它使用了全局替换。如果没有使用流的好方法,那么安全解析字符串的答案就可以了。

【问题讨论】:

所以你可以从返回值为空的情况下检测到错误。你是问如果发现返回值为null怎么办? @Makketronix 不。我在问如何处理这种格式错误的 JSON。 我为什么要这样做? as ask 问题没有表明将响应作为字符串读取并使用.Replace 不适用于您的情况。所以看到这个问题的其他人都不会理解它与大量其他“解析 JSONP”问题有什么不同,以及这个问题是否更适合他们的情况。再一次 - 你对你的帖子的原始版本表现出强烈的依恋,所以我怀疑其他人是否愿意在你的 cmets 中编辑它以澄清你到底在看什么以及为什么阅读为字符串不是答案。 @user6694745 不是格式错误,只是 JSONP 不是 JSON,不要再批评那些试图帮助你的人了。 @user6694745 如果您觉得这个问题不再反映您的需求和风格,另一种选择是请求与您的帐户解除关联 - 如果您想这样做,请标记帖子以引起版主注意并解释。跨度> 【参考方案1】:

看起来它正在返回JSONP。有点奇怪,默认情况下 Web 服务会执行此操作,而您不包括“?回调”。无论如何,如果就是这样,您可以轻松地使用正则表达式来剥离方法调用:

var x = WebServiceCall();
x = Regex.Replace(x, @"^.+?\(|\)$", "");

【讨论】:

我不是直接用字符串操作的。正则表达式没有关于流的词。虽然我没有检查你的正则表达式确实处理'parseResponse'。 好的,先转换成字符串?有什么问题?是的,正则表达式适用于任何 JSONP。 注意问题,Method Deserialize 不带字符串参数。 是的,我明白了。 :) 你为什么不能打电话给JsonConvert.DeserializeObject<MyType>(someString),它接受一个字符串?如果您想直接从响应中获取流并将其放入 JSON 阅读器,那么它不会起作用。你必须先处理它。 newtonsoft.com/json/help/html/DeserializeObject.htm @user6694745 - “方法反序列化不采用字符串参数” - JsonSerializer.Deserialize 有一个采用 TextReader 的重载,它可以是 StringReader【参考方案2】:

如果我将您的问题解释如下:

我正在尝试从 Stream 反序列化一些 JSON。 “JSON”实际上是 JSONP 格式,因此包含一些我想忽略的前缀和后缀文本。如何跳过前缀和后缀文本,同时仍然直接从流中读取和反序列化,而不是将整个流加载到字符串中?

然后您可以使用以下扩展方法从 JSONP 流中反序列化您的 JSON:

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;
        
    

注意事项:

在构造 JsonTextReader 之前,我构造了一个 StreamReader 并跳过了流中的第一个 '(' 字符。这会将StreamReader 定位在实际 JSON 的开头。

在反序列化之前,我设置JsonSerializerSettings.CheckAdditionalContent = false 告诉序列化程序忽略 JSON 内容结束后的任何字符。奇怪的是,尽管默认值似乎已经是false,但还是有必要明确地执行此操作,因为underlying field is nullable。

通过将StringReader 传递给DeserializeEmbeddedJsonP&lt;T&gt;(TextReader reader);,可以使用相同的代码从string 反序列化嵌入的JSONP。这样做可以避免通过修剪前缀和后缀文本来创建新字符串,因此即使是较小的字符串也可以提高性能和内存使用。

工作示例.Net fiddle。

【讨论】:

我自己也有类似的解决方案,虽然有点笨拙。我在流中使用 while 循环“跳过”'('。CheckAdditionalContent 选项是另一件事,它花了我一些时间。你的回答会为我节省很多时间。你的回答有点证实了我的思路。还有一个 SkupPast()方法确实增加了一定的优雅度。除此之外,您的答案简短,结构化,带有演示。总之:好。所有接受和支持它的理由。谢谢。

以上是关于如何反序列化 JSONP 响应(最好使用 JsonTextReader 而不是字符串)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 node.js + Express 中同时支持 json 和 jsonp 响应?

如何使用 Gson @SerializedName 注释在 Kotlin 中反序列化嵌套的 Json API 响应

将 JSON 响应保存为变量以使用 SwiftyJSON 反序列化

如何根据反序列化json字符串的响应在两个类之间切换?

无法反序列化 JSON 响应

无法反序列化 text/html json 响应