2010-07-02 18 views
16

Angenommen, ich verwende Googles Gson-Bibliothek, um JSON in Java-Datenstrukturen zu parsen.Strenge JSON-Analyse mit Googles Gson?

Gibt es eine einfache Möglichkeit, eine Ausnahme auszulösen, wenn es ein Java-Feld gibt, das keinen entsprechenden JSON hat? Das heißt, ich möchte, dass JSON alle Felder in der Java-Struktur hat.

Antwort

14

Gson verfügt nicht über eine JSON-Schemaüberprüfungsfunktion, um anzugeben, dass ein bestimmtes Element vorhanden sein muss, und es gibt keine Möglichkeit anzugeben, dass ein Java-Member ausgefüllt werden muss. Es wäre vielleicht schön, eine solche Funktion verfügbar zu haben, beispielsweise mit einer @Required Annotation. Gehe weiter zu the Gson Issues List und bringe eine Verbesserungsanfrage ein.

Mit Gson können Sie erzwingen, dass angegebene JSON-Elemente mit einem benutzerdefinierten Deserializer vorhanden sind.

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

Ein bevorzugter Ansatz könnte die Verwendung einer API sein, die JSON Schema Validierung bietet. Jackson has at least a rudimentary implementation available. JSON Tools scheint einen reiferen zu haben.

Hier ist ein Beispiel mit 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

Obwohl Ihre vorgeschlagenen Gson Lösung funktioniert, wenn Sie die Art von einem anderen Gson Instanz erstellen, funktioniert es nicht wenn derselbe Kontext wiederverwendet wird. Es verursacht eine Endlosschleife. Durch das Erstellen eines neuen Gons verlieren Sie andere Konfigurationsoptionen, die Ihr ursprünglicher Gson hatte. – Moritz

0

Sie können rekursiv prüfen, ob die json enthält Felder, die nicht in der Klasse deklariert:

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; 

}