Gson doesn't have a JSON schema validation feature to specify that a particular element must be present, and it doesn't have a way to specify that a Java member must be populated. It might be nice to have such a feature available, such as with an @Required annotation. Head on over to the Gson Issues List and put in an enhancement request.
With Gson, you could enforce that specified JSON elements are present with a custom deserializer.
// output:
// [MyObject: element1=value1, element2=value2, element3=value3]
// [MyObject: element1=value1, element2=value2, element3=null]
// Exception in thread "main" com.google.gson.JsonParseException: Required Field Not Found: element2
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class Foo
{
static String jsonInput1 = "{\"element1\":\"value1\",\"element2\":\"value2\",\"element3\":\"value3\"}";
static String jsonInput2 = "{\"element1\":\"value1\",\"element2\":\"value2\"}";
static String jsonInput3 = "{\"element1\":\"value1\",\"element3\":\"value3\"}";
public static void main(String[] args)
{
GsonBuilder gsonBuilder = new GsonBuilder();
MyDeserializer deserializer = new MyDeserializer();
deserializer.registerRequiredField("element2");
gsonBuilder.registerTypeAdapter(MyObject.class, deserializer);
Gson gson = gsonBuilder.create();
MyObject object1 = gson.fromJson(jsonInput1, MyObject.class);
System.out.println(object1);
MyObject object2 = gson.fromJson(jsonInput2, MyObject.class);
System.out.println(object2);
MyObject object3 = gson.fromJson(jsonInput3, MyObject.class);
System.out.println(object3);
}
}
class MyObject
{
String element1;
String element2;
String element3;
@Override
public String toString()
{
return String.format(
"[MyObject: element1=%s, element2=%s, element3=%s]",
element1, element2, element3);
}
}
class MyDeserializer implements JsonDeserializer<MyObject>
{
List<String> requiredFields = new ArrayList<String>();
void registerRequiredField(String fieldName)
{
requiredFields.add(fieldName);
}
@Override
public MyObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
JsonObject jsonObject = (JsonObject) json;
for (String fieldName : requiredFields)
{
if (jsonObject.get(fieldName) == null)
{
throw new JsonParseException("Required Field Not Found: " + fieldName);
}
}
return new Gson().fromJson(json, MyObject.class);
}
}
A preferable approach might be to use an API that provides JSON Schema validation. Jackson has at least a rudimentary implementation available. JSON Tools looks to have a more mature one.
Here's an example with Jackson.
// output:
// Validating jsonInput1...
// Validating jsonInput2...
// Validating jsonInput3...
// $.element2: is missing and it is not optional
// [MyObject: element1=value1, element2=value2, element3=value3]
// [MyObject: element1=value1, element2=value2, element3=null]
// [MyObject: element1=value1, element2=null, element3=value3]
import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;
import eu.vahlas.json.schema.JSONSchema;
import eu.vahlas.json.schema.JSONSchemaProvider;
import eu.vahlas.json.schema.impl.JacksonSchemaProvider;
public class Foo
{
static String jsonSchema =
"{" +
"\"description\":\"Serialized MyObject Specification\"," +
"\"type\":[\"object\"]," +
"\"properties\":" +
"{" +
"\"element1\":{\"type\":\"string\"}," +
"\"element2\":{\"type\":\"string\",\"optional\":false}," +
"\"element3\":{\"type\":\"string\",\"optional\":true}" +
"}" +
"}";;
static String jsonInput1 = "{\"element1\":\"value1\",\"element2\":\"value2\",\"element3\":\"value3\"}";
static String jsonInput2 = "{\"element1\":\"value1\",\"element2\":\"value2\"}";
static String jsonInput3 = "{\"element1\":\"value1\",\"element3\":\"value3\"}";
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
JSONSchemaProvider schemaProvider = new JacksonSchemaProvider(mapper);
JSONSchema schema = schemaProvider.getSchema(jsonSchema);
System.out.println("Validating jsonInput1...");
validateAndLogErrors(jsonInput1, schema);
System.out.println("Validating jsonInput2...");
validateAndLogErrors(jsonInput2, schema);
System.out.println("Validating jsonInput3...");
validateAndLogErrors(jsonInput3, schema);
MyObject object1 = mapper.readValue(jsonInput1, MyObject.class);
System.out.println(object1);
MyObject object2 = mapper.readValue(jsonInput2, MyObject.class);
System.out.println(object2);
MyObject object3 = mapper.readValue(jsonInput3, MyObject.class);
System.out.println(object3);
}
static void validateAndLogErrors(String jsonInput, JSONSchema schema)
{
List<String> errors = schema.validate(jsonInput);
for (String error : errors)
{
System.out.println(error);
}
}
}
class MyObject
{
String element1;
String element2;
String element3;
void setElement1(String element1)
{
this.element1 = element1;
}
void setElement2(String element2)
{
this.element2 = element2;
}
void setElement3(String element3)
{
this.element3 = element3;
}
@Override
public String toString()
{
return String.format(
"[MyObject: element1=%s, element2=%s, element3=%s]",
element1, element2, element3);
}
}
Answer from Programmer Bruce on Stack OverflowGson doesn't have a JSON schema validation feature to specify that a particular element must be present, and it doesn't have a way to specify that a Java member must be populated. It might be nice to have such a feature available, such as with an @Required annotation. Head on over to the Gson Issues List and put in an enhancement request.
With Gson, you could enforce that specified JSON elements are present with a custom deserializer.
// output:
// [MyObject: element1=value1, element2=value2, element3=value3]
// [MyObject: element1=value1, element2=value2, element3=null]
// Exception in thread "main" com.google.gson.JsonParseException: Required Field Not Found: element2
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class Foo
{
static String jsonInput1 = "{\"element1\":\"value1\",\"element2\":\"value2\",\"element3\":\"value3\"}";
static String jsonInput2 = "{\"element1\":\"value1\",\"element2\":\"value2\"}";
static String jsonInput3 = "{\"element1\":\"value1\",\"element3\":\"value3\"}";
public static void main(String[] args)
{
GsonBuilder gsonBuilder = new GsonBuilder();
MyDeserializer deserializer = new MyDeserializer();
deserializer.registerRequiredField("element2");
gsonBuilder.registerTypeAdapter(MyObject.class, deserializer);
Gson gson = gsonBuilder.create();
MyObject object1 = gson.fromJson(jsonInput1, MyObject.class);
System.out.println(object1);
MyObject object2 = gson.fromJson(jsonInput2, MyObject.class);
System.out.println(object2);
MyObject object3 = gson.fromJson(jsonInput3, MyObject.class);
System.out.println(object3);
}
}
class MyObject
{
String element1;
String element2;
String element3;
@Override
public String toString()
{
return String.format(
"[MyObject: element1=%s, element2=%s, element3=%s]",
element1, element2, element3);
}
}
class MyDeserializer implements JsonDeserializer<MyObject>
{
List<String> requiredFields = new ArrayList<String>();
void registerRequiredField(String fieldName)
{
requiredFields.add(fieldName);
}
@Override
public MyObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
JsonObject jsonObject = (JsonObject) json;
for (String fieldName : requiredFields)
{
if (jsonObject.get(fieldName) == null)
{
throw new JsonParseException("Required Field Not Found: " + fieldName);
}
}
return new Gson().fromJson(json, MyObject.class);
}
}
A preferable approach might be to use an API that provides JSON Schema validation. Jackson has at least a rudimentary implementation available. JSON Tools looks to have a more mature one.
Here's an example with Jackson.
// output:
// Validating jsonInput1...
// Validating jsonInput2...
// Validating jsonInput3...
// $.element2: is missing and it is not optional
// [MyObject: element1=value1, element2=value2, element3=value3]
// [MyObject: element1=value1, element2=value2, element3=null]
// [MyObject: element1=value1, element2=null, element3=value3]
import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;
import eu.vahlas.json.schema.JSONSchema;
import eu.vahlas.json.schema.JSONSchemaProvider;
import eu.vahlas.json.schema.impl.JacksonSchemaProvider;
public class Foo
{
static String jsonSchema =
"{" +
"\"description\":\"Serialized MyObject Specification\"," +
"\"type\":[\"object\"]," +
"\"properties\":" +
"{" +
"\"element1\":{\"type\":\"string\"}," +
"\"element2\":{\"type\":\"string\",\"optional\":false}," +
"\"element3\":{\"type\":\"string\",\"optional\":true}" +
"}" +
"}";;
static String jsonInput1 = "{\"element1\":\"value1\",\"element2\":\"value2\",\"element3\":\"value3\"}";
static String jsonInput2 = "{\"element1\":\"value1\",\"element2\":\"value2\"}";
static String jsonInput3 = "{\"element1\":\"value1\",\"element3\":\"value3\"}";
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
JSONSchemaProvider schemaProvider = new JacksonSchemaProvider(mapper);
JSONSchema schema = schemaProvider.getSchema(jsonSchema);
System.out.println("Validating jsonInput1...");
validateAndLogErrors(jsonInput1, schema);
System.out.println("Validating jsonInput2...");
validateAndLogErrors(jsonInput2, schema);
System.out.println("Validating jsonInput3...");
validateAndLogErrors(jsonInput3, schema);
MyObject object1 = mapper.readValue(jsonInput1, MyObject.class);
System.out.println(object1);
MyObject object2 = mapper.readValue(jsonInput2, MyObject.class);
System.out.println(object2);
MyObject object3 = mapper.readValue(jsonInput3, MyObject.class);
System.out.println(object3);
}
static void validateAndLogErrors(String jsonInput, JSONSchema schema)
{
List<String> errors = schema.validate(jsonInput);
for (String error : errors)
{
System.out.println(error);
}
}
}
class MyObject
{
String element1;
String element2;
String element3;
void setElement1(String element1)
{
this.element1 = element1;
}
void setElement2(String element2)
{
this.element2 = element2;
}
void setElement3(String element3)
{
this.element3 = element3;
}
@Override
public String toString()
{
return String.format(
"[MyObject: element1=%s, element2=%s, element3=%s]",
element1, element2, element3);
}
}
You can recursively verify whether the json contains fields that are not declared in the class :
private static List<String> verifyElement(JsonObject element, Class klass) throws NoSuchFieldException, IllegalAccessException {
List<String> unknownFields = new ArrayList<>();
Set<String> classFields = new HashSet<>();
for (Field field : klass.getDeclaredFields()) {
if (!Modifier.isPublic(field.getModifiers())) {
throw new IllegalArgumentException("All fields must be public. Please correct this field :" + field);
}
}
for (Field field : klass.getFields()) {
classFields.add(field.getName());
}
// Verify recursively that the class contains every
for (Map.Entry<String, JsonElement> entry : element.entrySet()) {
if (!classFields.contains(entry.getKey())) {
unknownFields.add(klass.getCanonicalName() + "::" + entry.getKey() + "\n");
} else {
Field field = klass.getField(entry.getKey());
Class fieldClass = field.getType();
if (!fieldClass.isPrimitive() && entry.getValue().isJsonObject()) {
List<String> elementErrors = verifyElement(entry.getValue().getAsJsonObject(), fieldClass);
unknownFields.addAll(elementErrors);
}
}
}
return unknownFields;
}
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.
You should not use Gson to make such validation:
Gsonis 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:bncjbhjfjhjis deserialized as ajava.lang.Stringinstance. 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
The main scope of GSON and Jackson is to handle the serialization/deserialization of your data to/from JSON. Both the generation and validation of JSON Schemas are not their (main) concern.
As per comments on your other question (How to display default value in JSON Schema using Jackson), there are a couple of alternatives out there for the schema generation – the same applies to their validation.
List of Implementations (Generation & Validation)
As per R.Groote's answer, you can find some (but not all) of them on https://json-schema.org/implementations.html – both generation and validation libraries.
Validation
Personally, I've only used networknt/json-schema-validator so far and found it very intuitive and easy to use. It uses Jackson under the hood, which was practical in my use-case as I was already using the same library for the (de)serialization of data from/to JSON – so less additional dependencies.
Generation
Disclaimer: here comes the biased part as I'm the creator of victools/jsonschema-generator.
One example for a JSON Schema generation library would be victools/jsonschema-generator. That also uses Jackson internally, so again similar dependencies to the networknt/json-schema-validator.
The jsonschema-generator library does not populate the "default" value out-of-the-box, but you could easily configure it like this (based on the Jackson @JsonProperty annotation as per your other question):
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON);
configBuilder.forFields().withDefaultResolver(field -> {
JsonProperty annotation = field.getAnnotationConsideringFieldAndGetter(JsonProperty.class);
return annotation == null || annotation.defaultValue().isEmpty() ? null : annotation.defaultValue());
});
SchemaGenerator generator = new SchemaGenerator(configBuilder.build());
JsonNode jsonSchema = generator.generateSchema(YourClass.class);
System.out.println(jsonSchema.toString());
If you are catering for numeric default values, you might have to include some conditional parsing of the @JsonProperty.defaultValue String then.
Additionally, if you want some more specific handling of Jackson annotations you could also consider adding the optional victools/jsonschema-module-jackson as well then.
EDIT (as response to your concerns having to define the default values twice – in the code and on the annotation):
With the victools/jsonschema-generator you have full control over where the default value is coming from. One possible approach could be to look-up the default values from actual instances of the respective objects being encountered – here filtering by your own package to skip external types.
The following is just a rough example of what is possible:
ConcurrentMap<Class<?>, Object> instanceCache = new ConcurrentHashMap<>();
configBuilder.forFields().withDefaultResolver(field -> {
Class<?> declaringClass = field.getDeclaringType().getErasedType();
if (!field.isFakeContainerItemScope()
&& declaringClass.getName().startsWith("your.package")) {
MethodScope getter = field.findGetter();
if (getter != null) {
try {
Object instance = instanceCache.computeIfAbsent(declaringClass, declaringClass::newInstance);
Object defaultValue = getter.getRawMember().invoke(instance);
return defaultValue;
} catch (Exception ex) {
// most likely missing a no-args constructor
}
}
}
return null;
});
I prefer to use GSON or Jackson: - https://www.baeldung.com/jackson-vs-gson
You could use Justify or json-schema-validator see below links: - https://json-schema.org/implementations.html - Validate JSON schema compliance with Jackson against an external schema file
I hope the above links give you enough information