如何解析已经使用GSON转义引号的JSON

时间:2017-01-19 15:15:17

标签: java json gson

我有以下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("\\\"","\"")

2 个答案:

答案 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的自定义InputStreamReader并自行删除转义字符,这样您就不会需要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

    }

}