我有以下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].
修改
到目前为止,最好的解决方案是添加
compile 'org.apache.commons:commons-lang3:3.5'
依赖关系并使用gson.fromJson(StringEscapeUtils.unescapeJson(response.body()), RouteModel[].class)
或者只是简单地使用replace("\\\"","\"")
答案 0 :(得分:2)
使用disableHtmlEscaping
可以解决问题,而不会出现难看的变通方法。
我还使用prettyPrinting
来获得更好的输出....
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
gson.from(response.body(), RouteModel[].class)
答案 1 :(得分:1)
哦,欢迎来到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,\“Code \”:\“0320 \”,\“姓名\”:.....]
→
“[{\”X \“:24.0124010872935,\”Y \“:49.7740722529036,\”Code \“:\”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,\”Code \“:\”0320 \“,\”姓名\“:.....]”
→
[{“X”:24.0124010872935,“Y”:49.7740722529036,“Code”:“0320”,“Name”:.....]
类似于我的应用程序源代码中的示例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);
}
输出:
并[d 0320 =(49.7740722529036,24.0124010872935)GT;]
关于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
创建了一个要在后续步骤中解析的中间字符串,因此内存成本可能效率不高。要解决此问题并减少解析过程中消耗的内存大小,您可以编写一个可以识别JSON的自定义InputStream
或Reader
并自行删除转义字符,这样您就不会需要StringWrapperTypeAdapterFactory
和中间字符串。如上所述,流式时尚风格对于这种解析更好,以便从不必要的中间对象中节省内存。尽管InputStream
不是一个非常适合阅读字符数据的地方而且Reader
更适合这样的任务,简单的JSON-escaping 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
}
}