如何使用GSON在Java中检查JSON是否有效?

14

我有一个方法需要检查JSON是否有效,在如何在Java中检查给定字符串是否为有效的JSON上找到,但它不起作用。

public static boolean isJson(String Json) {
        Gson gson = new Gson();
        try {
            gson.fromJson(Json, Object.class);
            return true;
        } catch (com.google.gson.JsonSyntaxException ex) {
            return false;
        }
    }

如果我用这种方法对一些字符串进行操作,它总是返回 true。例如:

System.out.println(renderHtml.isJson("{\"status\": \"UP\"}"));

它给了我true,并且

System.out.println(renderHtml.isJson("bncjbhjfjhj"));

true 也给了我。


你在寻找模式验证吗? - efekctive
7个回答

12

您不应该使用Gson进行这样的验证:

  • Gson是一个执行反序列化的对象,因此将整个JSON作为对象反序列化到内存中。
  • Gson可能对某些无效的JSON格式不太严格,我之前并不知道:bncjbhjfjhj会反序列化成一个java.lang.String实例。惊喜!
private static final Gson gson = new Gson();

private static final String VALID_JSON = "{\"status\": \"UP\"}";
private static final String INVALID_JSON = "bncjbhjfjhj";

System.out.println(gson.fromJson(VALID_JSON, Object.class).getClass());
System.out.println(gson.fromJson(INVALID_JSON, Object.class).getClass());

输出:

类 com.google.gson.internal.LinkedTreeMap
类 java.lang.String

在这里,您可以使用JsonReader逐个读取传入的JSON标记,从而使得给定的JSON文档在语法上有效。

private static boolean isJsonValid(final String json)
        throws IOException {
    return isJsonValid(new StringReader(json));
}

private static boolean isJsonValid(final Reader reader)
        throws IOException {
    return isJsonValid(new JsonReader(reader));
}

private static boolean isJsonValid(final JsonReader jsonReader)
        throws IOException {
    try {
        JsonToken token;
        loop:
        while ( (token = jsonReader.peek()) != END_DOCUMENT && token != null ) {
            switch ( token ) {
            case BEGIN_ARRAY:
                jsonReader.beginArray();
                break;
            case END_ARRAY:
                jsonReader.endArray();
                break;
            case BEGIN_OBJECT:
                jsonReader.beginObject();
                break;
            case END_OBJECT:
                jsonReader.endObject();
                break;
            case NAME:
                jsonReader.nextName();
                break;
            case STRING:
            case NUMBER:
            case BOOLEAN:
            case NULL:
                jsonReader.skipValue();
                break;
            case END_DOCUMENT:
                break loop;
            default:
                throw new AssertionError(token);
            }
        }
        return true;
    } catch ( final MalformedJsonException ignored ) {
        return false;
    }
}

然后测试它:

System.out.println(isJsonValid(VALID_JSON));
System.out.println(isJsonValid(INVALID_JSON));

输出:

true
false


12

我发现使用org.json库可以找到解决方案,参考在Java中如何检查给定字符串是否为有效的JSON

public static boolean isJson(String Json) {
        try {
            new JSONObject(Json);
        } catch (JSONException ex) {
            try {
                new JSONArray(Json);
            } catch (JSONException ex1) {
                return false;
            }
        }
        return true;
    }

现在看起来是随机的字符串 bncjbhjfjhjfalse,而 {"status": "UP"} 是 true。


您预计使用此方法验证的最大JSON大小是多少? - Lyubomyr Shaydariv
不是很大,用于从Springboot应用程序中读取healthchecks。 三到四层嵌套。 - QkiZ
他们为此提供了一个特殊的JsonParser:http://www.javadoc.io/doc/com.google.code.gson/gson/2.8.2 - Andrew

8
我很惊讶的是,虽然GsonBuilder#setLenient声称:

默认情况下,Gson严格遵循RFC 4627中指定的JSON格式。此选项使解析器在接受内容方面更加宽容。

但事实上它总是宽容的。而且,任何对JsonReader.setLenient(false)的调用都会被完全忽略!

在浏览了众多相关问题几个被拒绝的拉取请求之后,最终找到了https://github.com/google/gson/issues/1208这个有意义的解决方法:

JakeWharton在2017年12月15日发表评论

您可以调用getAdapter(type).fromJson(gson.newJsonReader(input))而不仅仅是fromJson(input),以获得严格的解析。我们应该真正弃用所有fromJson方法,并添加默认为严格的新版本。

原因是很久以前做出的错误决定,我们现在无法更改 ;(

因此,这里是使用纯Gson解决方案进行严格JSON对象解析的详细测试案例。

import org.junit.Test;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import static org.junit.Assert.*;

public class JsonTest {

    private static final TypeAdapter<JsonObject> strictGsonObjectAdapter = 
            new Gson().getAdapter(JsonObject.class);

    public static JsonObject parseStrict(String json) {
        // https://dev59.com/JlgQ5IYBdhLWcg3wJgmO#47890960
        try {
            //return strictGsonObjectAdapter.fromJson(json); // this still allows multiple top level values (
            try (JsonReader reader = new JsonReader(new StringReader(json))) {
                JsonObject result = strictGsonObjectAdapter.read(reader);
                reader.hasNext(); // throws on multiple top level values
                return result;
            }
        } catch (IOException e) {
            throw new JsonSyntaxException(e);
        }
    }

    @Test
    public void testStrictParsing() {
        // https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/stream/JsonReader.html#setLenient-boolean-
        // Streams that start with the non-execute prefix, ")]}'\n".
        assertThrows(JsonSyntaxException.class, () -> parseStrict("){}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("]{}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("}{}"));
        // Streams that include multiple top-level values. With strict parsing, each stream must contain exactly one top-level value.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{}{}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{}[]null"));
        // Top-level values of any type. With strict parsing, the top-level value must be an object or an array.
        assertThrows(JsonSyntaxException.class, () -> parseStrict(""));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("null"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("Abracadabra"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("13"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("\"literal\""));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("[]"));
        // Numbers may be NaNs or infinities.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"number\": NaN}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"number\": Infinity}"));
        // End of line comments starting with // or # and ending with a newline character.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{//comment\n}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{#comment\n}"));
        // C-style comments starting with /* and ending with */. Such comments may not be nested.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{/*comment*/}"));
        // Names that are unquoted or 'single quoted'.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{a: 1}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{'a': 1}"));
        // Strings that are unquoted or 'single quoted'.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": str}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": ''}"));
        // Array elements separated by ; instead of ,.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": [1;2]}"));
        // Unnecessary array separators. These are interpreted as if null was the omitted value.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": [1,]}"));
        // Names and values separated by = or => instead of :.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\" = 13}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\" => 13}"));
        // Name/value pairs separated by ; instead of ,.
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": 1; \"b\": 2}"));

        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": }"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": ,}"));
        assertThrows(JsonSyntaxException.class, () -> parseStrict("{\"a\": 0,}"));

        assertTrue(parseStrict("{} ").entrySet().isEmpty());
        assertTrue(parseStrict("{\"a\": null} \n \n").get("a").isJsonNull());
        assertEquals(0, parseStrict("{\"a\": 0}").get("a").getAsInt());
        assertEquals("", parseStrict("{\"a\": \"\"}").get("a").getAsString());
        assertEquals(0, parseStrict("{\"a\": []}").get("a").getAsJsonArray().size());
    }

}

请注意,这确保了单个顶级对象。可以将JsonObject.class替换为JsonArray.classJsonElement.class,以允许顶级数组或null
上面的代码将JSON解析为JsonObject DOM表示形式。
下面的代码对常规字段映射进行了严格解析,生成自定义POJO。
// https://github.com/google/gson/issues/1208
private static final TypeAdapter<Pojo> strictGsonAdapter = new Gson().getAdapter(Pojo.class);

public static Pojo parsePayment(String json) throws IOException {
    return strictGsonAdapter.fromJson(json);
}

请注意,上面的测试使用了 JUnit 4.13-beta-3 中的 Assert.assertThrows - Vadzim

7

虽然这可能对你来说有些奇怪

"bncjbhjfjhj"

这确实是有效的 JSON,因为它是一个字符串,并且是唯一的字符串。

根据不那么新的 JSON RFC

JSON 文本是一个序列化的值。请注意,某些先前的 JSON 规范将 JSON 文本限制为对象或数组。在需要 JSON 文本的情况下仅生成对象或数组的实现将在这样的意义上是可互操作的,即所有实现都将接受这些符合 JSON 规范的文本。


1
尽管你说得对,但实际上OP问的是为什么Gson不将bncjbhjfjhj(无引号)作为无效的JSON文档拒绝。 - Lyubomyr Shaydariv
1
因为它是有效的JSON?它只是一个普通字符串。 - WilomGfx
2
OP 正在使用 renderHtml.isJson("bncjbhjfjhj") 进行测试,而不是使用 renderHtml.isJson("\"bncjbhjfjhj\"") - Lyubomyr Shaydariv
@WilomGfx 你说得对。我通过使用 org.json 库找到了解决方案。 - QkiZ
@LyubomyrShaydariv 我知道这个,但是你正在给GSON一个字符串,它将被反序列化为java.lang.string。根据规范这有点合理。 - WilomGfx
1
@WilomGfx 因为Gson与更低级别的JsonReader组件不同,所以无法正常工作。请使用任何JSON linter检查bncjbhjfjhj"bncjbhjfjhj"两者。 - Lyubomyr Shaydariv

2

this works for me

public static boolean isJson(String Json) {
    Gson gson = new Gson();
    try {
        gson.fromJson(Json, Object.class);
        Object jsonObjType = gson.fromJson(Json, Object.class).getClass();
        if(jsonObjType.equals(String.class)){
            return false;
        }
        return true;
    } catch (com.google.gson.JsonSyntaxException ex) {
        return false;
    }
}

2
虽然这段代码片段可能是解决方案,但包括解释真的有助于提高您的帖子质量。请记住,您正在回答未来读者的问题,而这些人可能不知道您的代码建议原因。 - Johan

1
如果您只想验证输入是否为有效的JSON,而不使用解析后的JSON数据,则最简单且可能是最高效的解决方案是:
public static boolean isValidJson(String input) {
    try (JsonReader reader = new JsonReader(new StringReader(input))) {
        reader.skipValue();
        return reader.peek() == JsonToken.END_DOCUMENT;
    } catch (IOException e) {
        return false;
    }
}

请注意,即使在非宽容模式下,JsonReader 也允许某些不符合规范的JSON字符串,请参见文档中列出的那些情况,这里是JsonReader.setLenient(boolean)
所有其他使用GsonJsonParser的解决方案可能都不能正确工作,因为这些类默认情况下是宽容的,而且无法进行配置。

0
/**
 * Verify if its a valid json object or array.
 *
 * Note:- strings or primitives are not considered as valid json.
 *
 * @param json  string to verify
 * @return      true if string is a valid json, otherwise false
 */
private boolean isValidJson(String json) {
    try {
        JsonElement jsonElement = gson.fromJson(json, JsonElement.class);
        if (!jsonElement.isJsonObject() && !jsonElement.isJsonArray()) return false;
    } catch (Exception e) {
        return false;
    }
    return true;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接