我正在 Java 中使用 Jackson 2.4 来做一些 JSON 跑腿工作。我使用 Apache HttpGet 调用远程服务器,使用 Jackson 将结果反序列化为 POJO,操作这些结果,然后使用 Jackson 对其进行序列化,以使用 HttpPost 推送回远程服务器。
我发现的问题是 Jackson 正在将 unicode 文字转换为 unicode 字符,由于两端的编码问题,我不需要它这样做。例如,我可能在 JSON 中包含以下内容:
"field1": "\u00a2"
但是 Jackson 在反序列化时将“\u00a2”转换为“¢”,这会导致远程服务器出现问题。它必须被维护为转义的 unicode。如果我使用 Apache EntityUtils(指定 UTF-8)之类的东西,或者甚至从 Web 浏览器进行调用来获取数据,则转义的 unicode 会被保留,因此我知道它是从服务器正确传入的。如果我让 Jackson 在响应上使用来自实体的输入流,它会自动进行转换。
我尝试使用显式设置为 UTF-8 的 JsonGenerator 来写入 HttpPost。它不起作用,远程服务器仍然拒绝它。我已经研究了 ObjectMapper 和 JsonParser 的配置选项,但我没有看到任何可以覆盖此行为的内容。当然,转义非 ASCII,但这不是我需要在这里做的。也许我遗漏了一些明显的东西,但我无法让 Jackson 反序列化这个字符串而不替换转义的 unicode。
编辑:好吧,我的错,唯一有问题的文字有 3 或 5 个前导斜杠,而不仅仅是一个。这有点奇怪,但 Java 似乎是在反序列化过程中默认解包它的东西,即使从服务器返回的原始文本保留了它。仍然不确定如何让 Java 在不检查大量文本的情况下保留它。
您所期望的超出了 Jackosn 的范围。 java在读取字符串的同时对其进行转换。出于同样的原因,如果您有一个值为
\u00a2
的属性文件并使用 jdk API 读取它,您将获得转换后的值。根据文件大小,您可以在将字符串传递给 Json 之前对 char \ 进行双重转义,或者使用反序列化器(仅适用于字符串)将字符串“转义”回来,如下所示:
谢谢你
package com.test.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.Map;
public class Jackson {
static ObjectMapper _MAPPER = new ObjectMapper();
public static void main(String[] args) throws Exception {
String json = "{\"field1\": \"\\u00a2\",\"field2\": \"\\u00a2 this\",\"numberField\": 121212}";
SimpleModule testModule
= new SimpleModule("StOvFl", _MAPPER.version()).addDeserializer(String.class,
new UnEscapedSerializaer());
_MAPPER.registerModule(testModule);
Map m = _MAPPER.readValue(json, new TypeReference<Map<String, Object>>() {
});
System.out.println("m" + m);
}
}
class UnEscapedSerializaer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String s = jp.getValueAsString();
return org.apache.commons.lang.StringEscapeUtils.StringEscapeUtils.escapeJava(s);
}
}
自定义 Jackson 行为的另一种方法是自定义
JsonParser
。查看jackson的JsonFactory、ReaderBasedJsonParser;的源代码
关键方法是
_finishString2()
,它用于执行“decodeEscaped”,因此我们可以编写一个JsonParser扩展ReaderBasedJsonParser并覆盖_finishString2
方法:
public class MyJsonParser extends ReaderBasedJsonParser {
@Override
protected void _finishString2() throws IOException {
char[] outBuf = _textBuffer.getCurrentSegment();
int outPtr = _textBuffer.getCurrentSegmentSize();
final int[] codes = _icLatin1;
final int maxCode = codes.length;
while (true) {
if (_inputPtr >= _inputEnd) {
if (!loadMore()) {
_reportInvalidEOF(": was expecting closing quote for a string value");
}
}
char c = _inputBuffer[_inputPtr++];
int i = (int) c;
if (i < maxCode && codes[i] != 0) {
if (i == INT_QUOTE) {
break;
} else {
//c = _decodeEscaped();
//do nth
}
}
// Need more room?
if (outPtr >= outBuf.length) {
outBuf = _textBuffer.finishCurrentSegment();
outPtr = 0;
}
// Ok, let's add char to output:
outBuf[outPtr++] = c;
}
_textBuffer.setCurrentLength(outPtr);
}
public static void main(String[] args) throws IOException {
String json = "{\"field1\": \"\\u00a2\",\"field2\": \"\\u00a2 this\",\"numberField\": 121212}";
ObjectMapper objectMapper = new ObjectMapper(new MyJsonParserFactory());
Object o = objectMapper.readValue(json, Object.class);
System.out.println(o);
}
}
完整演示代码这里
花了几个小时寻找解决方案,我找到了。
我有一些二进制数据。例如
0xab 0xa6 0xaa
我希望我的 json 看起来像这样:
{
"binary-data-as-unicode": "\u00ab\u00a6\u00aa"
}
此 json 的读者将摆脱
\u00
并将剩下的内容视为表示二进制数据的十六进制字符串。
为了使用 jackson ObjectMapper,我用十六进制数据的格式化 unicode 表示法字符串准备了一个 String 对象:
public static String toHexString(ByteArrayOutputStream stream) {
byte[] byteArray = stream.toByteArray();
StringBuilder hexString = new StringBuilder();
for (byte b : byteArray) {
hexString.append(String.format("\\u%04X", b & 0xFF));
}
return String.format("\"%s\"", hexString.toString());
}
然后,我用了:
// stream of type ByteArrayOutputStream
String unicodedString = toHexString(stream);
ObjectNode objNode = mapper.createObjectNode();
objNode.putRawValue("field-name", new RawValue(unicodedString));
这样,我就没有再逃避了,我得到了我想要的东西。