Jackson 解析 JSON 时出现错误“非法字符...仅允许常规空格”

问题描述 投票:0回答:8

我尝试从 URL 检索 JSON 数据,但收到以下错误:

Illegal character ((CTRL-CHAR, code 31)):
only regular white space (\r, \n,\t) is allowed between tokens

我的代码:

final URI uri = new URIBuilder(UrlConstants.SEARCH_URL)
      .addParameter("keywords", searchTerm)
      .addParameter("count", "50")
      .build();
  node = new ObjectMapper().readTree(new URL(uri.toString())); <<<<< THROWS THE ERROR

构造的 url 即 https://www.example.org/api/search.json?keywords=iphone&count=50

这里出了什么问题?我怎样才能成功解析这些数据?


进口:

import com.google.appengine.repackaged.org.codehaus.jackson.JsonNode;
import com.google.appengine.repackaged.org.codehaus.jackson.map.ObjectMapper;
import com.google.appengine.repackaged.org.codehaus.jackson.node.ArrayNode;
import org.apache.http.client.utils.URIBuilder;

响应示例

{
    meta: {
        indexAllowed: false
    },
    products: {
        products: [ 
            {
                id: 1,
                name: "Apple iPhone 6 16GB 4G LTE GSM Factory Unlocked"
            },
            {
                id: 2,
                name: "Apple iPhone 7 8GB 4G LTE GSM Factory Unlocked"
            }
        ]
    }
}
java json jackson objectmapper
8个回答
38
投票

我遇到了同样的问题,我发现它是由

Content-Encoding: gzip
标头引起的。客户端应用程序(抛出异常的地方)无法处理此内容编码。 FWIW 客户端应用程序正在使用
io.github.openfeign:feign-core:9.5.0
,并且该库似乎在压缩方面存在一些问题(link)。

您可以尝试将标头

Accept-Encoding: identity
添加到您的请求中,但是,并非所有 Web 服务器/Web 应用程序都配置正确,有些似乎忽略了此标头。有关如何防止压缩内容的更多详细信息,请参阅此问题


14
投票

我也有类似的问题。经过一番研究,我发现

restTemplate
使用了不支持gzip编码的
SimpleClientHttpRequestFactory
。要为您的响应启用 gzip 编码,您需要为其余模板对象设置一个新的请求工厂 -
HttpComponentsClientHttpRequestFactory

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());


8
投票

该消息应该非常不言自明:

您正在处理的 JSON 中存在非法字符(本例中为字符代码 31,即控制代码“Unit Separator”)。

换句话说,您收到的数据不是正确的 JSON。


背景:

JSON 规范 (RFC 7159) 说:

  1. JSON 语法

JSON 文本是一系列标记。该组令牌包括六个 结构字符、字符串、数字和三个文字名称。

[...]

任何内容之前或之后允许存在无关紧要的空格 六个结构特征。

ws = *(

%x20 / ;空间

%x09 / ;水平制表符

%x0A / ;换行或换行

%x0D);回车

换句话说:JSON 可能在 token 之间包含空格(“tokens”表示 JSON 的一部分,即列表、字符串等),但“空格”被定义为仅表示字符 Space、Tab、Line feed 和 Carriage返回。

您的文档包含其他内容(代码 31),其中仅允许空格,因此不是有效的 JSON。


解析这个:

不幸的是,您使用的 Jackson 库没有提供解析这种格式错误的数据的方法。要成功解析此内容,您必须在 Jackson 处理 JSON 之前对其进行过滤。

您可能必须使用标准 HTTP,例如使用标准 HTTP,自行从 REST 服务检索(伪)JSON。 java.net.HttpUrlConnection。然后适当过滤掉“坏”字符,并将结果字符串传递给 Jackson。如何做到这一点完全取决于您如何使用 Jackson。

如果您遇到问题,请随时提出单独的问题:-)。


6
投票

我也有同样的问题。设置Gzip后就修复了。请参考我的代码

public String sendPostRequest(String req) throws Exception {

    // Create connection
    URL urlObject = new URL(mURL);
    HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection();
    connection.setRequestMethod("POST");
    connection.setRequestProperty("Content-Type", "application/json");
    connection.setRequestProperty("Content-Length", Integer.toString(req.getBytes().length));
    connection.setRequestProperty("Content-Language", "en-US");
    connection.setUseCaches(false);
    connection.setDoOutput(true);

    // Send request
    DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
    wr.writeBytes(req);
    wr.close();

    //Response handling
    InputStream responseBody                = null;
    if (isGzipResponse(connection)) {
        responseBody                = new GZIPInputStream(connection.getInputStream());         
    }else{
        responseBody = connection.getInputStream();
    }
    convertStreamToString(responseBody);

    return response.toString();

}

protected boolean isGzipResponse(HttpURLConnection con) {
    String encodingHeader = con.getHeaderField("Content-Encoding");
    return (encodingHeader != null && encodingHeader.toLowerCase().indexOf("gzip") != -1);
}

public void convertStreamToString(InputStream in) throws Exception {
    if (in != null) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int length = 0;
        while ((length = in.read(buffer)) != -1) {
            baos.write(buffer, 0, length);
        }

        response = new String(baos.toByteArray());

        baos.close();

    } else {
        response = null;
    }

}

1
投票

我在 Spring Boot 应用程序中遇到了与 zalando 日志相同的问题,在仔细阅读此处的答案后,我意识到,必须在之后应用响应拦截器,无论如何处理解压:

@Configuration
public class RestTemplateConfig {

   [....]

   @Bean
   public RestTemplate restTemplate() {
       return new RestTemplateBuilder()
               .requestFactory(new MyRequestFactorySupplier())
               .build();
   }

   class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {
       @Override
       public ClientHttpRequestFactory get() {
           CloseableHttpClient client = HttpClientBuilder.create()
                   .addInterceptorFirst(logbookHttpRequestInterceptor)
        // wrong:  .addInterceptorFirst(logbookHttpResponseInterceptor)
                   .addInterceptorLast(logbookHttpResponseInterceptor)
                   .build();
           HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = 
                  new HttpComponentsClientHttpRequestFactory(client);
           return clientHttpRequestFactory;
       }
   }
}

1
投票

使用FeignClient的请参考这个答案spring-feign-not-compressing-response

Spring 无法动态解码响应,因此您需要定义一个自定义 GZip 解码器。

帮我解决了。


0
投票

我们最近在集成测试中遇到了同样的问题。我们有一个

spring boot
应用程序,我们使用
wiremock
来模拟集成的微服务服务器。对于我们已实现的测试
get
请求之一,我们开始收到此错误。我们不得不将
wiremock
从 2.18.0 降级到 2.17.0,但效果很好。由于一些错误,
jackson parser
和特定版本的
wiremock
无法一起工作。我们没有时间弄清楚这些库中的错误到底是什么。


0
投票

我们正在使用压缩

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true

导致了这个。这种情况仅在从 OkHttp 迁移到 Apache Http 后发生,并且可能还涉及其他不再可重现的因素,因为这是很多猜测。

© www.soinside.com 2019 - 2024. All rights reserved.