解析 JSON 时 Jackson 错误 "Illegal character... only regular white space allowed"
Jackson error "Illegal character... only regular white space allowed" when parsing JSON
我正在尝试从 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"
}
]
}
}
该消息应该是不言自明的:
您正在处理的 JSON 中存在非法字符(在本例中为字符代码 31,即控制代码“单位分隔符”)。
换句话说,您收到的数据不正确JSON。
背景:
JSON 规范 (RFC 7159) 说:
- JSON Grammar
A JSON text is a sequence of tokens. The set of tokens includes six
tructural characters, strings, numbers, and three literal names.
[...]
Insignificant whitespace is allowed before or after any of the
six structural characters.
ws = *(
%x20 / ; Space
%x09 / ; Horizontal tab
%x0A / ; Line feed or New line
%x0D ) ; Carriage return
换句话说:JSON 可能在标记之间包含空格(“标记”表示 JSON 的一部分,即列表、字符串等),但“空格”被定义为仅表示字符 Space、Tab、Line feed 和 Carriage return.
您的文档包含其他内容(代码 31),其中只允许使用空格,因此无效 JSON。
要解析这个:
很遗憾,您使用的 Jackson 库没有提供解析这种格式错误的数据的方法。要成功解析它,您必须在 Jackson 处理之前过滤 JSON。
您可能必须自己从 REST 服务中检索(伪)JSON,使用标准 HTTP,例如java.net.HttpUrlConnection。然后适当过滤掉“坏”字符,并将结果字符串传递给 Jackson。如何做到这一点完全取决于您如何使用 Jackson。
如果您遇到问题,请随时提出单独的问题:-)。
我遇到了同样的问题,我发现它是由 Content-Encoding: gzip
header 引起的。客户端应用程序(抛出异常的地方)无法处理此 content-encoding。 FWIW 客户端应用程序正在使用 io.github.openfeign:feign-core:9.5.0
,并且此库似乎存在一些压缩问题 (link)。
您可以尝试将 header Accept-Encoding: identity
添加到您的请求中,但是,并非所有 Web servers/web 应用程序都已正确配置,有些应用程序似乎忽略了此 header .有关如何防止压缩内容的更多详细信息,请参阅 this question。
我遇到了同样的问题。设置 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;
}
}
我遇到了类似的问题。经过一些研究,我发现 restTemplate 使用不支持 gzip 编码的 SimpleClientHttpRequestFactory。要为您的响应启用 gzip 编码,您需要为其余模板对象设置一个新的请求工厂 - HttpComponentsClientHttpRequestFactory。
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
我们最近在集成测试中遇到了同样的问题。我们有一个 spring boot
应用程序,我们使用 wiremock
来模拟一个集成的微服务服务器。对于我们实施的测试 get
请求之一,我们开始收到此错误。我们不得不将 wiremock
从 2.18.0 降级到 2.17.0,它运行良好。由于一些错误,jackson parser
和那个特定版本的 wiremock
不能一起工作。我们没有时间弄清楚这些库中的错误到底是什么。
我的 spring 启动应用程序中的 zalando logbook 也有同样的问题,在仔细阅读这里的答案后,我意识到,响应拦截器必须在 after 解压缩的一切:
@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;
}
}
}
使用FeignClient的请参考此回答
Spring 无法动态解码响应,因此您需要定义自定义 GZip 解码器。
帮我解决了。
我正在尝试从 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"
}
]
}
}
该消息应该是不言自明的:
您正在处理的 JSON 中存在非法字符(在本例中为字符代码 31,即控制代码“单位分隔符”)。
换句话说,您收到的数据不正确JSON。
背景:
JSON 规范 (RFC 7159) 说:
- JSON Grammar
A JSON text is a sequence of tokens. The set of tokens includes six tructural characters, strings, numbers, and three literal names.
[...]
Insignificant whitespace is allowed before or after any of the six structural characters.
ws = *(
%x20 / ; Space
%x09 / ; Horizontal tab
%x0A / ; Line feed or New line
%x0D ) ; Carriage return
换句话说:JSON 可能在标记之间包含空格(“标记”表示 JSON 的一部分,即列表、字符串等),但“空格”被定义为仅表示字符 Space、Tab、Line feed 和 Carriage return.
您的文档包含其他内容(代码 31),其中只允许使用空格,因此无效 JSON。
要解析这个:
很遗憾,您使用的 Jackson 库没有提供解析这种格式错误的数据的方法。要成功解析它,您必须在 Jackson 处理之前过滤 JSON。
您可能必须自己从 REST 服务中检索(伪)JSON,使用标准 HTTP,例如java.net.HttpUrlConnection。然后适当过滤掉“坏”字符,并将结果字符串传递给 Jackson。如何做到这一点完全取决于您如何使用 Jackson。
如果您遇到问题,请随时提出单独的问题:-)。
我遇到了同样的问题,我发现它是由 Content-Encoding: gzip
header 引起的。客户端应用程序(抛出异常的地方)无法处理此 content-encoding。 FWIW 客户端应用程序正在使用 io.github.openfeign:feign-core:9.5.0
,并且此库似乎存在一些压缩问题 (link)。
您可以尝试将 header Accept-Encoding: identity
添加到您的请求中,但是,并非所有 Web servers/web 应用程序都已正确配置,有些应用程序似乎忽略了此 header .有关如何防止压缩内容的更多详细信息,请参阅 this question。
我遇到了同样的问题。设置 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;
}
}
我遇到了类似的问题。经过一些研究,我发现 restTemplate 使用不支持 gzip 编码的 SimpleClientHttpRequestFactory。要为您的响应启用 gzip 编码,您需要为其余模板对象设置一个新的请求工厂 - HttpComponentsClientHttpRequestFactory。
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
我们最近在集成测试中遇到了同样的问题。我们有一个 spring boot
应用程序,我们使用 wiremock
来模拟一个集成的微服务服务器。对于我们实施的测试 get
请求之一,我们开始收到此错误。我们不得不将 wiremock
从 2.18.0 降级到 2.17.0,它运行良好。由于一些错误,jackson parser
和那个特定版本的 wiremock
不能一起工作。我们没有时间弄清楚这些库中的错误到底是什么。
我的 spring 启动应用程序中的 zalando logbook 也有同样的问题,在仔细阅读这里的答案后,我意识到,响应拦截器必须在 after 解压缩的一切:
@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;
}
}
}
使用FeignClient的请参考此回答
Spring 无法动态解码响应,因此您需要定义自定义 GZip 解码器。
帮我解决了。