2010-07-02 16 views
16

Supponiamo che io stia usando la libreria Gson di Google per analizzare JSON in strutture di dati Java.Strict JSON che analizza con Gson di Google?

C'è un modo semplice per generare un'eccezione se esiste un campo Java che non ha JSON corrispondente? Cioè, desidero richiedere al JSON di avere tutti i campi nella struttura Java.

risposta

14

Gson non ha una funzione di convalida dello schema JSON per specificare che un particolare elemento deve essere presente e non ha un modo per specificare che un membro Java deve essere popolato. Potrebbe essere utile disporre di una funzione come questa, ad esempio con un'annotazione @Required. Andare su the Gson Issues List e inserire una richiesta di miglioramento.

Con Gson, è possibile applicare gli elementi JSON specificati con un deserializzatore personalizzato.

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

Un approccio potrebbe essere preferibile utilizzare un'API che fornisce JSON Schema convalida. Jackson has at least a rudimentary implementation available. JSON Tools sembra avere uno più maturo.

Ecco un esempio con 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

Anche se la soluzione GSON proposto funziona quando si crea il tipo da un'altra istanza GSON, non funziona riutilizzando lo stesso contesto. Causa un ciclo infinito. Creando un nuovo gson perdi altre opzioni di configurazione che il tuo gson originale aveva. – Moritz

0

È ricorsivamente possibile verificare se il JSON contiene campi che non sono dichiarati nella 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; 

}