How to check if JSON is valid in Java using GSON?
Asked Answered
P

7

14

I have method that have to check if JSON is valid, found on How to check whether a given string is valid JSON in Java but it doesn't work.

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;
        }
    }

If I use this method with some string it always returns true. For example:

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

it gave me true, and

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

gave me true also.

Pereyra answered 5/4, 2017 at 14:19 Comment(1)
are you looking for schema validation?Drain
P
12

I found solution but using org.json library, according to How to check whether a given string is valid JSON in Java

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

Now random looking string bncjbhjfjhj is false and {"status": "UP"} is true.

Pereyra answered 5/4, 2017 at 14:58 Comment(3)
What is the size of the biggest JSON you expect to validate with this method?Limburg
Not big. It's for reading of healthchecks from Springboot applications. 3 or 4 levels of nesting.Pereyra
They have a special JsonParser for this: javadoc.io/doc/com.google.code.gson/gson/2.8.2Triumph
L
12

You should not use Gson to make such validation:

  • Gson is an object that performs deserialization therefore it deserializes entire JSON as an object in memory.
  • Gson, and I didn't know it, may be not very strict for some invalid JSONs: bncjbhjfjhj is deserialized as a java.lang.String instance. Surprise-surprise!
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());

Output:

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

What you can do here is using JsonReader to read incoming JSON token by token thus making if the given JSON document is syntactically valid.

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;
    }
}

And then test it:

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

Output:

true
false

Limburg answered 5/4, 2017 at 14:48 Comment(0)
P
12

I found solution but using org.json library, according to How to check whether a given string is valid JSON in Java

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

Now random looking string bncjbhjfjhj is false and {"status": "UP"} is true.

Pereyra answered 5/4, 2017 at 14:58 Comment(3)
What is the size of the biggest JSON you expect to validate with this method?Limburg
Not big. It's for reading of healthchecks from Springboot applications. 3 or 4 levels of nesting.Pereyra
They have a special JsonParser for this: javadoc.io/doc/com.google.code.gson/gson/2.8.2Triumph
S
7

While it might be weird to you

"bncjbhjfjhj"

Is indeed valid json, as it is a string, and its the only string.

According to the not so new JSON RFC

A JSON text is a serialized value. Note that certain previous specifications of JSON constrained a JSON text to be an object or an array. Implementations that generate only objects or arrays where a JSON text is called for will be interoperable in the sense that all implementations will accept these as conforming JSON texts.

Shelli answered 5/4, 2017 at 14:25 Comment(6)
Despite you're correct on that, the OP is actually asking why Gson does not reject bncjbhjfjhj (no quotes) as an invalid JSON document.Limburg
Because it is valid JSON ? its just a plain stringShelli
The OP was testing it with renderHtml.isJson("bncjbhjfjhj"), not with renderHtml.isJson("\"bncjbhjfjhj\"").Limburg
@Shelli you right. I found solution by using org.json library.Pereyra
@LyubomyrShaydariv i am aware of that, but you're giving GSON a String, which will be deserialized to a java.lang.string. Which kinda makes sense according to the specShelli
@Shelli Because Gson does not work the same as a more low-level Gson component JsonReader does. Check both bncjbhjfjhj and "bncjbhjfjhj" with any JSON linter.Limburg
M
7

I was quite surprised that while GsonBuilder#setLenient states

By default, Gson is strict and only accepts JSON as specified by RFC 4627. This option makes the parser liberal in what it accepts.

It appears to be flat-out lie as it is actually always lenient. Moreover, even any call to JsonReader.setLenient(false) is completely ignored!

After some browsing in numerous related issues and several rejected pull requests "due to legacy compatibility reasons" I've at last found https://github.com/google/gson/issues/1208 with sensible workaround:

JakeWharton commented on 15 Dec 2017

You can call getAdapter(type).fromJson(gson.newJsonReader(input)) instead of just fromJson(input) to get strict parsing. We should really deprecate all of the fromJson methods and add new versions that are strict by default.

The reason is bad decisions long ago that we can no longer change ;(

So here is pure Gson solution for strict json object parsing with extensive test case.

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://mcmap.net/q/75703/-how-to-check-if-json-is-valid-in-java-using-gson/47890960#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());
    }

}

Note the this ensures single top level object. It's possible to replace JsonObject.class with JsonArray.class or JsonElement.class to allow top level array or null.

The code above parses JSON to JsonObject DOM representation.

The code below does strict parsing into custom POJO with conventional fields mapping.

// 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);
}
Melloney answered 19/12, 2017 at 16:14 Comment(1)
Note that test above uses Assert.assertThrows from JUnit 4.13-beta-3.Melloney
E
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;
    }
}
Endow answered 2/7, 2019 at 8:35 Comment(1)
While this code snippet may be the solution, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.Moser
T
1

If you just want to verify that the input is valid JSON without using the parsed JSON data, then the easiest and probably most performant solution to this is:

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;
    }
}

Note however that even a JsonReader in non-lenient mode allows certain JSON strings which are not valid per specification, see the documentation for JsonReader.setLenient(boolean) which lists those cases.

All other solutions using either Gson or JsonParser will probably not work correctly because those classes are lenient by default and this behavior cannot be configured.

Tyndale answered 18/9, 2022 at 22:35 Comment(0)
C
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;
}
Calumnious answered 23/3, 2023 at 9:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.