2010-07-02 8 views
16

Supposons que j'utilise la bibliothèque Gson de Google pour analyser JSON en structures de données Java.Stricte JSON analyse avec Google Gson?

Existe-t-il un moyen facile de lancer une exception si un champ Java n'a pas de JSON correspondant? Autrement dit, je souhaite que le JSON ait tous les champs de la structure Java.

Répondre

14

Gson n'a pas de fonctionnalité de validation de schéma JSON pour spécifier qu'un élément particulier doit être présent et il n'a aucun moyen de spécifier qu'un membre Java doit être rempli. Il peut être intéressant de disposer d'une telle fonctionnalité, par exemple avec une annotation @Required. Rendez-vous sur the Gson Issues List et mettez une demande d'amélioration. Avec Gson, vous pouvez appliquer les éléments JSON spécifiés à un désérialiseur personnalisé.

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

Une approche préférable peut-être d'utiliser une API qui fournit la JSON Schema. Jackson has at least a rudimentary implementation available. JSON Tools semble avoir un plus mature.

Voici un exemple avec 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); 
    } 
} 
+2

Bien que votre solution gson proposée fonctionne lorsque vous créez le type par une autre instance gson, il ne fonctionne pas lors de la réutilisation du même contexte. Cela provoque une boucle infinie. En créant un nouveau gson, vous perdez d'autres options de configuration que votre gson d'origine. – Moritz

0

Vous pouvez récursive vérifier si le JSON contient des champs qui ne sont pas déclarés dans la classe:

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; 

}