While putting together a presentation, I have a situation in which I expect an exception, but I am getting none, when I run my corresponding unit test. What I am doing is incrementally modifying a bean. In this version of the Product and Accessory classes, I have removed the setters/getters for all the properties (save for a setter for one of the Product properties). I have previously converted my classes to use field access notation. So, since I have removed the setters/getters, I am expecting an exception because the field visibility modifiers are private.
Here is the Accessory class:
public class Accessory {
private String name
private BigDecimal cost
private BigDecimal price
public Accessory() {
this.cost = BigDecimal.ZERO
this.price = BigDecimal.ZERO
}
public Accessory(String name, BigDecimal cost, BigDecimal price) {
this.name = name
this.cost = cost
this.price = price
}
#Override
public int hashCode() {
final int prime = 31
int result = 1
result = prime * result + ((cost == null) ? 0 : cost.hashCode())
result = prime * result + ((name == null) ? 0 : name.hashCode())
result = prime * result + ((price == null) ? 0 : price.hashCode())
return result
}
public boolean equals(Accessory obj) {
return name == obj.name &&
cost == obj.cost &&
price == obj.price
}
#Override
public String toString() {
return "Accessory [" + "name=" + name + ", cost=" + cost + ", price=" + price + "]"
}
}
Here are snippets from the Product class:
public class Product {
private String model
private List<Accessory> accessories
private TreeMap<Integer, BigDecimal> priceBreaks
private BigDecimal cost
private BigDecimal price
...
public BigDecimal getAccessorizedCost() {
...
for (Accessory pkg : this.accessories) {
pkgCost = pkgCost.add pkg.cost
}
return pkgCost
}
...
}
I would expect that the line pkgCost = pkgCost.add pkg.cost in the above snippet would throw an exception. Likewise, I would think the following asserts in my unit test would do the same:
#Test public void canCreateDefaultInstance() {
assertNull "Default construction of class ${defaultProduct.class.name} failed to properly initialize model.", defaultProduct.model
assertTrue "Default construction of class ${defaultProduct.class.name} failed to properly initialize accessories.", defaultProduct.accessories.isEmpty()
assertTrue "Default construction of class ${defaultProduct.class.name} failed to properly initialize priceBreaks.", defaultProduct.priceBreaks.isEmpty()
assertEquals "Default construction of class ${defaultProduct.class.name} failed to properly initialize cost.", BigDecimal.ZERO, defaultProduct.cost as BigDecimal
assertEquals "Default construction of class ${defaultProduct.class.name} failed to properly initialize price.", BigDecimal.ZERO, defaultProduct.price as BigDecimal
}
Here are the metaclass properties and methods:
MetaClass Properties are:
[accessorizedCost, accessorizedPrice, class, priceBreaks]
MetaClass Methods are:
[__$swapInit, addPriceBreak, calcDiscountMultiplierFor, calcVolumePriceFor, equals, getAccessorizedCost, getAccessorizedPrice, getClass, getMetaClass, getProperty, hashCode, invokeMethod, notify, notifyAll, setMetaClass, setPriceBreaks, setProperty, toString, wait]
You can see, for example, that there are no properties nor corresponding getter/setters for the privately defined model, accessories, cost and price fields. So, like the line in the Product class not failing when referencing the cost property of Accessory, I do not understand how the unit tests can pass when there is not property nor getter/setters for these private fields.
I am compiling using Groovy 2.0.4 and running Eclipse.
What am I missing or not understanding?
Related
Goal
I am trying to push some data to a mongo db using mongojack.
I expect the result to be something like this in the db:
{
"_id": "840617013772681266",
"messageCount": 69420,
"seedCount": 18,
"prefix": "f!",
"language": "en"
}
Problem
Instead, I get this error in my console.
Caused by: java.lang.IllegalArgumentException: invalid hexadecimal representation of an ObjectId: [840617013772681266]
at org.bson.types.ObjectId.parseHexString(ObjectId.java:390)
at org.bson.types.ObjectId.<init>(ObjectId.java:193)
at org.mongojack.internal.ObjectIdSerializer.serialiseObject(ObjectIdSerializer.java:66)
at org.mongojack.internal.ObjectIdSerializer.serialize(ObjectIdSerializer.java:49)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
... 59 more
Code
This is the code that gets called when I try to create a new Guild in the db:
public static Guild getGuild(String id) throws ExecutionException {
return cache.get(id);
}
cache is the following (load get executed):
private static LoadingCache<String, Guild> cache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(
new CacheLoader<>() {
#Override
public Guild load(#NotNull String id) {
return findGuild(id).orElseGet(() -> new Guild(id, "f!"));
}
});
The findGuild method that gets called first:
public static Optional<Guild> findGuild(String id) {
return Optional.ofNullable(guildCollection.find()
.filter(Filters.eq("_id", id)).first());
}
And finally the Guild document.
#Getter
#Setter
public class Guild implements Model {
public Guild(String id, String prefix) {
this.id = id;
this.prefix = prefix;
}
public Guild() {
}
private String id;
/*
If a Discord guild sent 1,000,000,000 messages per second,
it would take roughly 292471 years to reach the long primitive limit.
*/
private long messageCount;
private long seedCount;
// The default language is specified in BotValues.java's bot.yaml.
private String language;
private String prefix;
#ObjectId
#JsonProperty("_id")
public String getId() {
return id;
}
#ObjectId
#JsonProperty("_id")
public void setId(String id) {
this.id = id;
}
}
What I've tried
I've tried multiple things, such as doing Long.toHexString(Long.parseLong(id)) truth is I don't understand the error completely and after seeing documentation I'm left with more questions than answers.
ObjectId is a 12-byte value that is commonly represented as a sequence of 24 hex digits. It is not an integer.
You can either create ObjectId values using the appropriate ObjectId constructor or parse a 24-hex-digit string. You appear to be trying to perform an integer conversion to ObjectId which generally isn't a supported operation.
You can technically convert the integer 840617013772681266 to an ObjectId by zero-padding it to 12 bytes, but standard MongoDB driver tooling doesn't do that for you and considers this invalid input (either as an integer or as a string) for conversion to ObjectId.
Example in Ruby:
irb(main):011:0> (v = '%x' % 840617013772681266) + '0' * (24 - v.length)
=> "baa78b862120032000000000"
Note that while the resulting value would be parseable as an ObjectId, it isn't constructed following the ObjectId rules and thus the value cannot be sensibly decomposed into the ObjectId components (machine id, counter and a random value).
I want to use Jackson to deserialize and later serialize jsons using Kotlin's data classes. It's important that I maintain the distinction between an explicitly null property, and a property which was omitted in the original json.
I have a large domain model (50+ classes) built almost entirely out of Kotlin data classes. Kotlin's data classes provide a lot of useful functionalities that I need to use elsewhere in my program, and for that reason I'd like to keep them instead of converting my models.
I've currently got this code working, but only for Java classes or using Kotlin properties declared in the body of the Kotlin class, and not working for properties declared in the constructor. For Kotlin's data class utility functions to work, all properties must be declared in the constructor.
Here's my object mapper setup code:
val objectMapper = ObjectMapper()
objectMapper.registerModule(KotlinModule())
objectMapper.registerModule(Jdk8Module())
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS)
objectMapper.configOverride(Optional::class.java).includeAsProperty =
JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, null)
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true)
objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
objectMapper.nodeFactory = JsonNodeFactory.withExactBigDecimals(true)
Here are my test classes:
TestClass1.java
public class TestClass1 {
public TestClass1() {}
public TestClass1(int intVal, Optional<Double> optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
public Integer intVal;
public Optional<Double> optDblVal;
}
TestClasses.kt
data class TestClass2(val intVal: Int?, val optDblVal: Optional<Double>?)
class TestClass3(val intVal: Int?, val optDblVal: Optional<Double>?)
class TestClass4 {
val intVal: Int? = null
val optDblVal: Optional<Double>? = null
}
and here are my tests:
JsonReserializationTests.kt
#Test
fun `Test 1 - Explicit null Double reserialized as explicit null`() {
val inputJson = """
{
"intVal" : 7,
"optDblVal" : null
}
""".trimIndent()
val intermediateObject = handler.objectMapper.readValue(inputJson, TestClassN::class.java)
val actualJson = handler.objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(intermediateObject)
.replace("\r", "")
assertEquals(inputJson, actualJson)
}
#Test
fun `Test 2 - Missing Double not reserialized`() {
val inputJson = """
{
"intVal" : 7
}
""".trimIndent()
val intermediateObject = handler.objectMapper.readValue(inputJson, TestClassN::class.java)
val actualJson = handler.objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(intermediateObject)
.replace("\r", "")
assertEquals(inputJson, actualJson)
}
Test Results for each class
Let's talk about TestClass2.
If you convert Kotlin code to Java Code, you can find the reason.
Intellij offers a converting tool for Kotlin. You can find it from the menu Tools -> Kotlin -> Show Kotlin Bytecode.
Here is a Java code from the TestClass2 Kotlin code.
public final class TestClass2 {
#Nullable
private final Integer intVal;
#Nullable
private final Optional optDblVal;
#Nullable
public final Integer getIntVal() {
return this.intVal;
}
#Nullable
public final Optional getOptDblVal() {
return this.optDblVal;
}
public TestClass2(#Nullable Integer intVal, #Nullable Optional optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
#Nullable
public final Integer component1() {
return this.intVal;
}
#Nullable
public final Optional component2() {
return this.optDblVal;
}
#NotNull
public final TestClass2 copy(#Nullable Integer intVal, #Nullable Optional optDblVal) {
return new TestClass2(intVal, optDblVal);
}
// $FF: synthetic method
public static TestClass2 copy$default(TestClass2 var0, Integer var1, Optional var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.intVal;
}
if ((var3 & 2) != 0) {
var2 = var0.optDblVal;
}
return var0.copy(var1, var2);
}
#NotNull
public String toString() {
return "TestClass2(intVal=" + this.intVal + ", optDblVal=" + this.optDblVal + ")";
}
public int hashCode() {
Integer var10000 = this.intVal;
int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
Optional var10001 = this.optDblVal;
return var1 + (var10001 != null ? var10001.hashCode() : 0);
}
public boolean equals(#Nullable Object var1) {
if (this != var1) {
if (var1 instanceof TestClass2) {
TestClass2 var2 = (TestClass2)var1;
if (Intrinsics.areEqual(this.intVal, var2.intVal) && Intrinsics.areEqual(this.optDblVal, var2.optDblVal)) {
return true;
}
}
return false;
} else {
return true;
}
}
The original code is too long, so here is the constructor only.
public TestClass2(#Nullable Integer intVal, #Nullable Optional optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
Since Jackson library cannot create an instance without parameters because there is no non-parameter constructor, it will try to create a new instance with some parameters. For test case 2, JSON has only one parameter so that it will look for a one-parameter constructor, but there is no so that it will throw an exception. This is also why test case 1 is passed.
Therefore, what you have to do is that you have to give all default values to all the parameters of data class to make a non-parameter constructor like the below code.
data class TestClass2(val intVal: Int? = null, val optDblVal: Optional<Double>? = null)
Then, if you see in Java code, the class will have a non-parameter constructor.
public TestClass2(#Nullable Integer intVal, #Nullable Optional optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
// $FF: synthetic method
public TestClass2(Integer var1, Optional var2, int var3, DefaultConstructorMarker var4)
{
if ((var3 & 1) != 0) {
var1 = (Integer)null;
}
if ((var3 & 2) != 0) {
var2 = (Optional)null;
}
this(var1, var2);
}
public TestClass2() {
this((Integer)null, (Optional)null, 3, (DefaultConstructorMarker)null);
}
So, if you still want to use Kotlin data class, you have to give default values to all the variables.
data class Example(
val greeting: Optional<String>? = null
)
This allows you to distinguish all three cases in the JSON:
non-null value ({"greeting":"hello"} → greeting.isPresent() == true)
null value ({"greeting":null} → greeting.isPresent() == false)
not present ({ } → greeting == null)
This is just a concise summary of what #Pemassi offered, with the key insight being to make a default null assignment to the nullable Optional<T> member.
Note that the semantics of .isPresent() is potentially confusing to a casual observer, since it does not refer to the presence of a value in the JSON.
A full unit test demonstration is here.
I just found the Kotiln Plugin that makes no-argument constructor for data class automatically. This should help you without much editing. However, this is not a good design pattern, so I still recommend giving default value to all members.
Here is a link for the Kotlin NoArg Plugin
https://kotlinlang.org/docs/reference/compiler-plugins.html#no-arg-compiler-plugin
I am new to java & Junit. Please help to write Junit test case to test the CargoBO method where Equals & Hashcode functionalities are not implemented.Basically i need to compare 2 objects using Equalbuilder class in junit.
public class CargoBO {
public Cargo cargoDetails(String name,String desc,double length,double width) {
return new Cargo(name,desc,length,width);
}
}
public class CargoJUnit {
Cargo cargo;
#Before
public void createObjectForCargo() {
cargo = new Cargo("audi","des",123.00,234.00);
}
#Test
public void testCargoDetails() {
CargoBO cargoBO = new CargoBO();
//assertTrue(cargo.equals(cargoBO.cargoDetails("audi","des",123.00,234.00)));
Assert.assertEquals(cargo, cargoBO.cargoDetails("audi","des",123.00,234.00));
}
}
Correct test case for your scenario is
#Test
public void testCargoDetails() {
String name = "test name";
String desc = "desc";
double length = 10d;
double width = 100d;
Cargo result = cargoBO.cargoDetails(name, desc, length, width);
Assert.assertEquals(cargo.getName, name);
Assert.assertEquals(cargo.getDesc, desc);
Assert.assertEquals(cargo.getLength, length);
Assert.assertEquals(cargo.getWidth, width);
}
You are testing a method which accepts parameters and calls a constructor passing those parameters.
Your test should be verifying if the given parameters are correctly passed by the method or not.
How should one deal with Gsonand required versus optional fields?
Since all fields are optional, I can't really fail my network request based on if the response json contains some key, Gsonwill simply parse it to null.
Method I am using gson.fromJson(json, mClassOfT);
For example if I have following json:
{"user_id":128591, "user_name":"TestUser"}
And my class:
public class User {
#SerializedName("user_id")
private String mId;
#SerializedName("user_name")
private String mName;
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
}
Is the any option to get Gson to fail if json would not contain user_id or user_name key?
There can be many cases where you might need at least some values to be parsed and other one could be optional?
Is there any pattern or library to be used to handle this case globally?
Thanks.
As you note, Gson has no facility to define a "required field" and you'll just get null in your deserialized object if something is missing in the JSON.
Here's a re-usable deserializer and annotation that will do this. The limitation is that if the POJO required a custom deserializer as-is, you'd have to go a little further and either pass in a Gson object in the constructor to deserialize to object itself or move the annotation checking out into a separate method and use it in your deserializer. You could also improve on the exception handling by creating your own exception and pass it to the JsonParseException so it can be detected via getCause() in the caller.
That all said, in the vast majority of cases, this will work:
public class App
{
public static void main(String[] args)
{
Gson gson =
new GsonBuilder()
.registerTypeAdapter(TestAnnotationBean.class, new AnnotatedDeserializer<TestAnnotationBean>())
.create();
String json = "{\"foo\":\"This is foo\",\"bar\":\"this is bar\"}";
TestAnnotationBean tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);
json = "{\"foo\":\"This is foo\"}";
tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);
json = "{\"bar\":\"This is bar\"}";
tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface JsonRequired
{
}
class TestAnnotationBean
{
#JsonRequired public String foo;
public String bar;
}
class AnnotatedDeserializer<T> implements JsonDeserializer<T>
{
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
T pojo = new Gson().fromJson(je, type);
Field[] fields = pojo.getClass().getDeclaredFields();
for (Field f : fields)
{
if (f.getAnnotation(JsonRequired.class) != null)
{
try
{
f.setAccessible(true);
if (f.get(pojo) == null)
{
throw new JsonParseException("Missing field in JSON: " + f.getName());
}
}
catch (IllegalArgumentException ex)
{
Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return pojo;
}
}
Output:
This is foo
this is bar
This is foo
null
Exception in thread "main" com.google.gson.JsonParseException: Missing field in JSON: foo
Answer of Brian Roach is very good, but sometimes it's also necessary to handle:
properties of model's super class
properties inside of arrays
For these purposes the following class can be used:
/**
* Adds the feature to use required fields in models.
*
* #param <T> Model to parse to.
*/
public class JsonDeserializerWithOptions<T> implements JsonDeserializer<T> {
/**
* To mark required fields of the model:
* json parsing will be failed if these fields won't be provided.
* */
#Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
#Target(ElementType.FIELD) // to make annotation accessible through reflection
public #interface FieldRequired {}
/**
* Called when the model is being parsed.
*
* #param je Source json string.
* #param type Object's model.
* #param jdc Unused in this case.
*
* #return Parsed object.
*
* #throws JsonParseException When parsing is impossible.
* */
#Override
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
// Parsing object as usual.
T pojo = new Gson().fromJson(je, type);
// Getting all fields of the class and checking if all required ones were provided.
checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);
// Checking if all required fields of parent classes were provided.
checkSuperClasses(pojo);
// All checks are ok.
return pojo;
}
/**
* Checks whether all required fields were provided in the class.
*
* #param fields Fields to be checked.
* #param pojo Instance to check fields in.
*
* #throws JsonParseException When some required field was not met.
* */
private void checkRequiredFields(#NonNull Field[] fields, #NonNull Object pojo)
throws JsonParseException {
// Checking nested list items too.
if (pojo instanceof List) {
final List pojoList = (List) pojo;
for (final Object pojoListPojo : pojoList) {
checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
checkSuperClasses(pojoListPojo);
}
}
for (Field f : fields) {
// If some field has required annotation.
if (f.getAnnotation(FieldRequired.class) != null) {
try {
// Trying to read this field's value and check that it truly has value.
f.setAccessible(true);
Object fieldObject = f.get(pojo);
if (fieldObject == null) {
// Required value is null - throwing error.
throw new JsonParseException(String.format("%1$s -> %2$s",
pojo.getClass().getSimpleName(),
f.getName()));
} else {
checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
checkSuperClasses(fieldObject);
}
}
// Exceptions while reflection.
catch (IllegalArgumentException | IllegalAccessException e) {
throw new JsonParseException(e);
}
}
}
}
/**
* Checks whether all super classes have all required fields.
*
* #param pojo Object to check required fields in its superclasses.
*
* #throws JsonParseException When some required field was not met.
* */
private void checkSuperClasses(#NonNull Object pojo) throws JsonParseException {
Class<?> superclass = pojo.getClass();
while ((superclass = superclass.getSuperclass()) != null) {
checkRequiredFields(superclass.getDeclaredFields(), pojo);
}
}
}
First of all the interface (annotation) to mark required fields with is described, we'll see an example of its usage later:
/**
* To mark required fields of the model:
* json parsing will be failed if these fields won't be provided.
* */
#Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
#Target(ElementType.FIELD) // to make annotation accessible throw the reflection
public #interface FieldRequired {}
Then deserialize method is implemented. It parses json strings as usual: missing properties in result pojo will have null values:
T pojo = new Gson().fromJson(je, type);
Then the recursive check of all fields of the parsed pojo is being launched:
checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);
Then we also check all fields of pojo's super classes:
checkSuperClasses(pojo);
It's required when some SimpleModel extends its SimpleParentModel and we want to make sure that all properties of SimpleModel marked as required are provided as SimpleParentModel's ones.
Let's take a look on checkRequiredFields method. First of all it checks if some property is instance of List (json array) - in this case all objects of the list should also be checked to make sure that they have all required fields provided too:
if (pojo instanceof List) {
final List pojoList = (List) pojo;
for (final Object pojoListPojo : pojoList) {
checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
checkSuperClasses(pojoListPojo);
}
}
Then we are iterating through all fields of pojo, checking if all fields with FieldRequired annotation are provided (what means these fields are not null). If we have encountered some null property which is required - an exception will be fired. Otherwise another recursive step of the validation will be launched for current field, and properties of parent classes of the field will be checked too:
for (Field f : fields) {
// If some field has required annotation.
if (f.getAnnotation(FieldRequired.class) != null) {
try {
// Trying to read this field's value and check that it truly has value.
f.setAccessible(true);
Object fieldObject = f.get(pojo);
if (fieldObject == null) {
// Required value is null - throwing error.
throw new JsonParseException(String.format("%1$s -> %2$s",
pojo.getClass().getSimpleName(),
f.getName()));
} else {
checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
checkSuperClasses(fieldObject);
}
}
// Exceptions while reflection.
catch (IllegalArgumentException | IllegalAccessException e) {
throw new JsonParseException(e);
}
}
}
And the last method should be reviewed is checkSuperClasses: it just runs the similar required fields validation checking properties of pojo's super classes:
Class<?> superclass = pojo.getClass();
while ((superclass = superclass.getSuperclass()) != null) {
checkRequiredFields(superclass.getDeclaredFields(), pojo);
}
And finally lets review some example of this JsonDeserializerWithOptions's usage. Assume we have the following models:
private class SimpleModel extends SimpleParentModel {
#JsonDeserializerWithOptions.FieldRequired Long id;
#JsonDeserializerWithOptions.FieldRequired NestedModel nested;
#JsonDeserializerWithOptions.FieldRequired ArrayList<ListModel> list;
}
private class SimpleParentModel {
#JsonDeserializerWithOptions.FieldRequired Integer rev;
}
private class NestedModel extends NestedParentModel {
#JsonDeserializerWithOptions.FieldRequired Long id;
}
private class NestedParentModel {
#JsonDeserializerWithOptions.FieldRequired Integer rev;
}
private class ListModel {
#JsonDeserializerWithOptions.FieldRequired Long id;
}
We can be sure that SimpleModel will be parsed correctly without exceptions in this way:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(SimpleModel.class, new JsonDeserializerWithOptions<SimpleModel>())
.create();
gson.fromJson("{\"list\":[ { \"id\":1 } ], \"id\":1, \"rev\":22, \"nested\": { \"id\":2, \"rev\":2 }}", SimpleModel.class);
Of course, provided solution can be improved and accept more features: for example - validations for nested objects which are not marked with FieldRequired annotation. Currently it's out of answer's scope, but can be added later.
(Inspired by Brian Roache's answer.)
It seems that Brian's answer doesn't work for primitives because the values can be initialized as something other than null (e.g. 0).
Moreover, it seems like the deserializer would have to be registered for every type. A more scalable solution uses TypeAdapterFactory (as below).
In certain circumstances, it is safer to whitelist exceptions from required fields (i.e. as JsonOptional fields) rather than annotating all fields as required.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface JsonOptional {
}
Though this approach can easily be adapted for required fields instead.
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class AnnotatedTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
Set<Field> requiredFields = Stream.of(rawType.getDeclaredFields())
.filter(f -> f.getAnnotation(JsonOptional.class) == null)
.collect(Collectors.toSet());
if (requiredFields.isEmpty()) {
return null;
}
final TypeAdapter<T> baseAdapter = (TypeAdapter<T>) gson.getAdapter(rawType);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter jsonWriter, T o) throws IOException {
baseAdapter.write(jsonWriter, o);
}
#Override
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);
if (jsonElement.isJsonObject()) {
ArrayList<String> missingFields = new ArrayList<>();
for (Field field : requiredFields) {
if (!jsonElement.getAsJsonObject().has(field.getName())) {
missingFields.add(field.getName());
}
}
if (!missingFields.isEmpty()) {
throw new JsonParseException(
String.format("Missing required fields %s for %s",
missingFields, rawType.getName()));
}
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(AnnotatedTypeAdapterFactory.this, typeToken);
return delegate.fromJsonTree(jsonElement);
}
};
}
}
This is my simple solution that creates a generic solution with minimum coding.
Create #Optional annotation
Mark First Optional. Rest are assumed optional. Earlier are assumed required.
Create a generic 'loader' method that checks that source Json object has a value. The loop stops once an #Optional field is encountered.
I am using subclassing so the grunt work is done in the superclass.
Here is the superclass code.
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Optional {
public boolean enabled() default true;
}
and the grunt work method
#SuppressWarnings ("unchecked")
public <T> T payload(JsonObject oJR,Class<T> T) throws Exception {
StringBuilder oSB = new StringBuilder();
String sSep = "";
Object o = gson.fromJson(oJR,T);
// Ensure all fields are populated until we reach #Optional
Field[] oFlds = T.getDeclaredFields();
for(Field oFld:oFlds) {
Annotation oAnno = oFld.getAnnotation(Optional.class);
if (oAnno != null) break;
if (!oJR.has(oFld.getName())) {
oSB.append(sSep+oFld.getName());
sSep = ",";
}
}
if (oSB.length() > 0) throw CVT.e("Required fields "+oSB+" mising");
return (T)o;
}
and an example of usage
public static class Payload {
String sUserType ;
String sUserID ;
String sSecpw ;
#Optional
String sUserDev ;
String sUserMark ;
}
and the populating code
Payload oPL = payload(oJR,Payload.class);
In this case sUserDev and sUserMark are optional and the rest required. The solution relies on the fact that the class stores the Field definitions in the declared order.
I searched a lot and found no good answer. The solution I chose is as follows:
Every field that I need to set from JSON is an object, i.e. boxed Integer, Boolean, etc. Then, using reflection, I can check that the field is not null:
public class CJSONSerializable {
public void checkDeserialization() throws IllegalAccessException, JsonParseException {
for (Field f : getClass().getDeclaredFields()) {
if (f.get(this) == null) {
throw new JsonParseException("Field " + f.getName() + " was not initialized.");
}
}
}
}
From this class, I can derive my JSON object:
public class CJSONResp extends CJSONSerializable {
#SerializedName("Status")
public String status;
#SerializedName("Content-Type")
public String contentType;
}
and then after parsing with GSON, I can call checkDeserialization and it will report me if some of the fields is null.
In an attempt to find another issue, my tests came up with the following bit of code.
public class TestPersistance {
private static final PersistenceManagerFactory PMF = JDOHelper.getPersistenceManagerFactory("datanucleus.properties");
public static final PersistenceManager pm = PMF.getPersistenceManager();
static final TestUserDataDB ud = new TestUserDataDB();
public static void main(String args[])
{
TestPersistance tp = new TestPersistance();
tp.createData();
}
#Test public void createData()
{
assertTrue("Null machined id at start", ud.machineId != null);
pm.currentTransaction().begin();
try
{
pm.makePersistent(ud);
}
finally
{
pm.currentTransaction().commit();
}
assertTrue("Null machined id at end", ud.machineId != null);
}
}
where the second assert fails. ie. my object that I am asking to be persisted is being changed by the makePersistent call. The data is being stored in the database.
Any ideas? Can any one confirm this.
using
jdo-api-3.0.jar
datanucleus-core-2.2.0-release.jar
datanucleus-enhancer-2.1.3.jar
datanucleus-rdbms-2.2.0-release.jar
mysql-connector-java-5.1.13.jar
in eclipse with MySql database.
#PersistenceCapable
public class TestUserDataDB {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
public Long id;
#Persistent
public String userid = "test1";
#Persistent
public String machineId = "test2";
// local userid
#Persistent
public long uid = 1L;
#Persistent
public long systemTime = 123L;
public long chk = 1234L;
public long createTime = System.currentTimeMillis();
public TestUserDataDB()
{
}
#Override
public String toString() {
return "TestUserDataDB [chk=" + chk + ", createTime=" + createTime
+ ", id=" + id + ", machineId=" + machineId + ", systemTime="
+ systemTime + ", uid=" + uid + ", userid=" + userid + "]";
}
}
Properties file is
javax.jdo.PersistenceManagerFactoryClass=org.datanucleus.jdo.JDOPersistenceManagerFactory
datanucleus.metadata.validate=false
javax.jdo.option.ConnectionDriverName=com.mysql.jdbc.Driver
javax.jdo.option.ConnectionURL=jdbc:mysql://localhost/test
javax.jdo.option.ConnectionUserName=root
javax.jdo.option.ConnectionPassword=yeahRight
datanucleus.autoCreateSchema=true
datanucleus.validateTables=false
datanucleus.validateConstraints=false
Why are you accessing fields directly ? Is the accessing class declared as PersistenceAware ? Well it isn't so you can't do that - use the getters.
What is "ud" object state before persist ? (transient?) what is it after persist ? (hollow?) What does the log say ? Chances are that it is in hollow state and then you access a field directly and it has no value (by definition, as per the spec) ... but since you didn't bother calling the getter it hasn't a chance to retrieve the value. And you likely also don't have "RetainValues" persistent property set
Suggest you familiarise yourself with the JDO spec and object lifecycle states
In some cases, it is necessary to access deserialized objects' attributes directly (i.e. if using GSON library for JSON serialization). In that case you can use:
MyClass copy = myPersistencyManager.detachCopy(myRetrievedInstance);