如何使用GSON解析带有转义引号的JSON



我有以下JSON[{"X":24.0124010872935,"Y":49.7740722529036,"Code":"0320","Name": .....]

我试图将其解析为

Gson gson = new Gson();
gson.fromJson(response.body(), RouteModel[].class)

得到了异常

Caused by: com.google.gson.stream.MalformedJsonException: Expected name at line 1 column 3 path $[0].

EDIT到目前为止,最好的解决方案是添加compile 'org.apache.commons:commons-lang3:3.5'依赖性和使用gson.fromJson(StringEscapeUtils.unescapeJson(response.body()), RouteModel[].class)

或者只是简单地使用replace("\"",""")

使用disableHtmlEscaping应该可以解决问题,而不会出现难看的解决方法。此外,我使用prettyPrinting来获得更好的输出。。。。

Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
gson.from(response.body(), RouteModel[].class)

哦,欢迎来到SimpleRide API的华丽世界。:D一年半前,在推出我的Android应用程序之前,我第一次尝试解决这个问题,当时我在编码方面玩得很开心。我怀疑那些家伙返回这样一个字符串是为了只在前面使用JSON.parse。因此,最简单的(但不是最有效的)方法是将响应解析为字符串以"规范化"它们,然后解析规范化的JSON文档。

为了解析您的(请参阅下面的注释)JSON,需要将JSON输入流表示为JSON字符串文字输入流。这可以通过连接输入流轻松完成。

final class FixedInputStreams {
private static final byte[] e1DoubleQuoteArray = """.getBytes();
private FixedInputStreams() {
}
static InputStream fixInputStream(final InputStream inputStream) {
return concatInputStreams(
new ByteArrayInputStream(e1DoubleQuoteArray),
inputStream,
new ByteArrayInputStream(e1DoubleQuoteArray)
);
}
private static InputStream concatInputStreams(final InputStream... inputStreams) {
return concatInputStreams(asList(inputStreams).iterator());
}
// Iterator and not an iterable by design
private static InputStream concatInputStreams(final Iterator<? extends InputStream> inputStreamsIterator) {
return new SequenceInputStream(asEnumeration(inputStreamsIterator));
}
private static <T> Enumeration<T> asEnumeration(final Iterator<T> iterator) {
return new Enumeration<T>() {
@Override
public boolean hasMoreElements() {
return iterator.hasNext();
}
@Override
public T nextElement() {
return iterator.next();
}
};
}
}

这个类所做的只是修复这种格式错误的输入流,以便模拟JSON字符串输入流。因此,有了上面的输入流,您的JSON就变成了合法的JSON字符串:

[{\"X\":24.0124010872935,\"Y\":49.7740722529036,\"代码\":\"0320\",\"名称\":…..]

"[{\"X\":24.0124010872935,\"Y\":49.7740722529036,\"代码\":\"0320\",\"名称\":…..]">

现在您必须解析此字符串来提取规范化的JSON。MalformedJsonTypeAdapterFactory表示一个合成的Gson类型适配器工厂,它的唯一职责是解析JSON字符串文本,然后将后者解析为格式良好的DTO。

final class StringWrapperTypeAdapterFactory
implements TypeAdapterFactory {
private final Gson realGson;
private StringWrapperTypeAdapterFactory(final Gson realGson) {
this.realGson = realGson;
}
static TypeAdapterFactory getStringWrapperTypeAdapterFactory(final Gson realGson) {
return new StringWrapperTypeAdapterFactory(realGson);
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
return new TypeAdapter<T>() {
@Override
public void write(final JsonWriter out, final T value) {
throw new UnsupportedOperationException();
}
@Override
public T read(final JsonReader in) {
final String jsonDocument = realGson.fromJson(in, String.class);
return realGson.fromJson(jsonDocument, typeToken.getType());
}
};
}
}

所以这里的想法是:

"[{\"X\":24.0124010872935,\"Y\":49.7740722529036,\"代码\":\"0320\",\"名称\":…..]">

[{"X":24.0124010872935,"Y":49.7740722529036,"代码":"0320","名称":……]

一个类似于我的应用程序源代码中的示例DTO类:

final class NamedPoint {
@SerializedName("X")
final double longitude = Double.valueOf(0); // disabling primitives inlining
@SerializedName("Y")
final double latitude = Double.valueOf(0);
@SerializedName("Code")
final String code = null;
@Override
public String toString() {
return '<' + code + "=(" + latitude + ',' + longitude + ")>";
//                   ^__ See? Even this string is aware of the issue
}
}

最后,一般配置和工作流程现在如下所示:

static final Type namedPointListType = new TypeToken<List<NamedPoint>>() {
}.getType();
static final Gson realGson = new GsonBuilder()
// ... your Gson configuration here ...
.create();
static final Gson stringWrapperGson = new GsonBuilder()
.registerTypeAdapterFactory(getStringWrapperTypeAdapterFactory(realGson))
.create();
// or `new ByteArrayInputStream(jsonSource.getBytes())` to test quickly
final InputStream malformedInputStream = ...;
try ( final InputStream fixedInputStream = fixInputStream(malformedInputStream);
final Reader jsonReader = new BufferedReader(new InputStreamReader(fixedInputStream))) {
final List<NamedPoint> namedPoints = stringWrapperGson.fromJson(jsonReader, namedPointListType);
out.println(namedPoints);
}

输出:

[lt;0320=(49.7740722529036,24.0124010872935)>]

关于SimpleRide API的几条评论:

  • 我不确定您现在是否需要通过"-串联来"修复"输入流,因为API似乎会自己包装它(由于JSON.parse?)。你可以用wget http://82.207.107.126:13541/SimpleRide/LAD/SM.WebApi/api/Schedule/?routeId=713032&code=0298这样的东西很容易地检查它。也许某个Content-Type可以调整响应格式
  • 由于StringWrapperTypeAdapterFactory创建了要在进一步步骤中解析的中间字符串,因此由于内存成本的原因,它可能不是有效的。为了克服这个问题并减少解析过程中消耗的内存大小,您可以编写一个自定义的InputStreamReader,它可以支持JSON,并剥离转义字符本身,因此您甚至不需要StringWrapperTypeAdapterFactory和中间字符串

编辑:

如上所述,流式风格更适合这种解析,以节省不必要的中间对象的内存。尽管InputStream不是读取字符数据的合适位置,而且Reader更适合这样的任务,但简单的JSON转义InputStream更容易实现:

final class StringWrapperInputStream
extends InputStream {
private final InputStream inputStream;
private State state = State.PRE_INIT;
private StringWrapperInputStream(final InputStream inputStream) {
this.inputStream = inputStream;
}
static InputStream getStringWrapperInputStream(final InputStream inputStream) {
return new StringWrapperInputStream(inputStream);
}
@Override
public int read()
throws IOException {
for ( ; ; ) {
switch ( state ) {
case PRE_INIT:
final int chPreInit = inputStream.read();
if ( chPreInit == -1 ) {
return -1;
}
if ( isWhitespace(chPreInit) ) {
continue;
}
if ( chPreInit == '"' ) {
state = IN_PROGRESS;
} else {
throw new IllegalArgumentException("char=" + chPreInit);
}
continue;
case IN_PROGRESS:
final int chInProgress1 = inputStream.read();
if ( chInProgress1 == -1 ) {
return -1;
}
if ( chInProgress1 == '"' ) {
state = DONE;
continue;
}
if ( chInProgress1 != '\' ) {
return chInProgress1;
}
final int chInProgress2 = inputStream.read();
if ( chInProgress2 == -1 ) {
return -1;
}
if ( chInProgress2 == '"' ) {
return '"';
}
break;
case DONE:
return -1;
default:
throw new AssertionError(state);
}
}
}
enum State {
PRE_INIT,
IN_PROGRESS,
DONE
}
}

最新更新