package data; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.Type; /** * JSON deserializer which validates that all fields are present. * * @author Peter Wu. */ public class ValidatingJsonDeserializer implements JsonDeserializer { @Override public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { T obj = new Gson().fromJson(je, type); checkObject(je, obj.getClass()); return obj; } private void checkObject(JsonElement je, Class type) { if (!je.isJsonObject()) { throw new JsonParseException("Expected object: " + type.getName()); } JsonObject jsonObj = je.getAsJsonObject(); for (Field f : type.getDeclaredFields()) { if (!jsonObj.has(f.getName())) { if (f.getAnnotation(Nullable.class) != null) { // null allowed, skip continue; } throw new JsonParseException("Missing field: " + f.getName()); } tryValidateProperty(jsonObj.get(f.getName()), f); // TODO: validate type? } } private void tryValidateProperty(JsonElement je, Field f) { // assume that this annotation is only applied to objects Validator v = f.getAnnotation(Validator.class); if (v != null) { Class type = f.getType(); if (type.isArray()) { // the class expects an array, so the value must have one too. if (!je.isJsonArray()) { throw new JsonParseException("Not an array: " + f.getName()); } JsonArray ja = je.getAsJsonArray(); type = type.getComponentType(); // for each array element, check if the object is valid. for (JsonElement arr_je : ja) { checkObject(arr_je, type); } } else { // not an array, assume a verifiable object checkObject(je, type); } } } /** * Marks a member as object that should be validated too. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Validator { Class deserializer() default ValidatingJsonDeserializer.class; } /** * Marks a member as nullable, that is, it can be missing from the JSON * object. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Nullable { } }