1

I tried using @NotNull annotation but it did not work. If any field is missing in the JsonString, it gives null(String) or zero(int). But what I want is if any field is missing in the JsonStr defined in the class should throw an exception.

Add: My PojoClass may have object reference or multilevel object reference. I am using Gson for the conversion of the String to obj. for more clarification, I have added my code below:

JsonStr:

{
   "name":"John",
   "id":1,
   "roll":100,
   "c":{
      "city":"Dhaka",
      "school":"B. govt. School"
   }
}

Code:

public class C {

    private String city;
    private String school;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

}

ClassB:

public class B {

    private String name;
    private int id;
    @NotNull
    private int roll;
    private C c;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getRoll() {
        return roll;
    }

    public void setRoll(int roll) {
        this.roll = roll;
    }

    public C getC() {
        return c;
    }

    public void setC(C c) {
        this.c = c;
    }

}

MainClass:

try {
                B obj = new B();
                String str = "{\"name\":\"John\",\"id\":1,\"c\":{\"city\":\"dhaka\",\"school\":\"school\"}}";
                obj = gson.fromJson(str, B.class);

            } catch (RuntimeException e) {
                 System.out.println("exception Message");

            }

For the field roll, I used @NotNull to throw an exception if that field is not present in the JsonStr but it gives 0 value without throwing any exception.

How can I implement that?

Please don't say this is duplicate, because I have seen these questions:

Micho
  • 3,929
  • 13
  • 37
  • 40
user404
  • 1,934
  • 1
  • 16
  • 32

2 Answers2

1

@NotNull is not a part of Gson, and it cannot handle it by default (unless you add support yourself). Also, nullability-check annotations should not be applied to primitives fields -- generally speaking it makes not much sense. Also, the default int fields values are 0, and Gson does not check if a particular field was read from a JSON document. However, you can easily implement it if you:

  • slightly change your mapping by changing the primitive int to its nullable wrapper Integer;
  • implement a custom post-processing validation type adapter.

For example,

final class Person {

    final String name = null;

    final int id = Integer.valueOf(0); // final primitive fields are inlined by javac

    @NotNull
    final Integer roll = null;

    @SerializedName("c")
    final Location location = null;

}
final class Location {

    final String city = null;

    final String school = null;

}

The next step is just creating your own type adapter to check the nullable fields.

final class NotNullTypeAdapterFactory
        implements TypeAdapterFactory {

    // The type adapter factory holds no state, so no need to instantiate it multiple times
    private static final TypeAdapterFactory notNullTypeAdapterFactory = new NotNullTypeAdapterFactory();

    private NotNullTypeAdapterFactory() {
    }

    static TypeAdapterFactory getNotNullTypeAdapterFactory() {
        return notNullTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        final Collection<Field> notNullFields = getNotNullFields(typeToken.getRawType());
        // If no @NotNull fields found, then just tell Gson to pick the next best type adapter
        if ( notNullFields.isEmpty() ) {
            return null;
        }
        // If there's at least one @NotNull field, get the original type adapter
        final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
        return new TypeAdapter<T>() {
            @Override
            public void write(final JsonWriter out, final T value)
                    throws IOException {
                delegateTypeAdapter.write(out, value);
            }

            @Override
            public T read(final JsonReader in)
                    throws IOException {
                try {
                    // Read the value ...
                    final T value = delegateTypeAdapter.read(in);
                    // ... and make some post-processing
                    for ( final Field f : notNullFields ) {
                        if ( f.get(value) == null ) {
                            throw new MalformedJsonException(f + " has no value");
                        }
                    }
                    return value;
                } catch ( final IllegalAccessException ex ) {
                    throw new IOException(ex);
                }
            }
        };
    }

    private static Collection<Field> getNotNullFields(final Class<?> clazz) {
        // Primitive types and java.lang.Object do not have @NotNull
        if ( clazz.isPrimitive() || clazz == Object.class ) {
            return emptyList();
        }
        // Scan the whole hierarchy from the bottom subclass to the top superclass (except java.lang.Object we mentioned above)
        final Collection<Field> notNullFields = new ArrayList<>();
        for ( Class<?> c = clazz; c != Object.class; c = c.getSuperclass() ) {
            for ( final Field f : c.getDeclaredFields() ) {
                if ( f.isAnnotationPresent(NotNull.class) ) {
                    // Don't forget to make private fields accessible
                    f.setAccessible(true);
                    notNullFields.add(f);
                }
            }
        }
        return notNullFields;
    }

}

Test (uses some Google Guava and custom resource readers):

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getNotNullTypeAdapterFactory())
        .create();

public static void main(final String... args)
        throws IOException {
    for ( final String resourceName : ImmutableList.of("file-with-roll.json", "file-without-roll.json") ) {
        System.out.println("Deserializing " + resourceName);
        try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q44362030.class, resourceName) ) {
            try {
                final Person person = gson.fromJson(jsonReader, Person.class);
                System.out.println(person.name + " " + person.roll);
            } catch ( final Exception ex ) {
                System.out.println("FAILED! " + ex.getMessage());
            }
        }
    }
}

Output:

Deserializing file-with-roll.json
John 100
Deserializing file-without-roll.json
FAILED! com.google.gson.stream.MalformedJsonException: final java.lang.Integer q44362030.Person.roll has no value

Similarly, you can create your another TypeAdapter to check all fields automatically not even needing @NotNull, but this needs a more complicated implementation.

Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
  • would you please like to explain a bit what is "Q44362030.class" in the try block of main method and the immutableList too? It gives error for the "getPackageResourceJsonReader" method. – user404 Jun 06 '17 at 05:41
  • 1
    @user404 It's just a custom resource reader factory method letting to reader these two JSON resources from the package the Q###.class is declared in. Its name derives from your question id at S.O. You can easily replace it with `new JsonReader` and pass anything to it. `ImmutableList` is just a list factory method from Google Guava and can be replaced with `asList`. Simply speaking, I just iterated over resource JSON documents borrowed from your question to test the solution. – Lyubomyr Shaydariv Jun 06 '17 at 06:17
  • thanks for the reply. Btw, I tried this way and it still gives exception though the roll is present in the file. my [Link](https://ideone.com/YNuMAM). I am almost lost here! – user404 Jun 06 '17 at 12:16
  • @user404 doesn't it have to be encapsulated in a class with the main method? – Lyubomyr Shaydariv Jun 06 '17 at 12:36
  • that's right, I did that within a class having main method. I only quoted this particular block. Sorry if that misinterprets. – user404 Jun 06 '17 at 13:27
  • @user404 Which exception exactly? – Lyubomyr Shaydariv Jun 06 '17 at 13:34
  • In the code snipped, I have directed a file with path **""D:\\file_with_roll.json""** , roll is present in the file and it should not throw any exception as every field is present. But it throws exception(exception msg as null). – user404 Jun 06 '17 at 13:48
  • @user404 _exception msg as null_ -- if I'm getting it right, you seem to have an issue somewhere around in your code. The exception thrown on no field always throws an exception with a message (`throw new MalformedJsonException(f + " has no value");`); – Lyubomyr Shaydariv Jun 06 '17 at 13:59
0

Have you try using Wrapper class instead primitive?

use this:

@NotNull private Integer roll;

Kenji Mukai
  • 599
  • 4
  • 8