I'm writing an SDK for a JSON API and I'm running into a seemingly weird problem. The API is quite strict in it's POST data validation and it does not allow certain parameters to be present when updating resources, like an id
. For this reason, I added @Expose(serialize = false)
the ID field of my resource class. It seems however, that it still serializes this field, causing the request to be rejected. The resource class roughly is as follows:
public class Organisation extends BaseObject
{
public static final Gson PRETTY_PRINT_JSON = new GsonBuilder()
.setPrettyPrinting()
.create();
@Expose(serialize = false)
@SerializedName("_id")
private String id;
@SerializedName("email")
private String email;
@SerializedName("name")
private String name;
@SerializedName("parent_id")
private String parentId;
public String toJson()
{
return PRETTY_PRINT_JSON.toJson(this);
}
}
My unit tests create an instance of Organisation
via the API, saves the newly created instance to the test class as a class parameter and calls an update method that will test the update implementation of the SDK by updating the new resource. This is where it goes wrong. Even though the toJson()
method is called on the new Organisation
to serialise it to JSON for the update request, the _id
field remains present, causing the API to decline the update. The test code is as follows. Notice the comments in the code.
@Test
public void testCreateUpdateAndDeleteOrganisation() throws RequestException
{
Organisation organisation = new Organisation();
organisation.setParentId(this.ORGANISATION_ID);
organisation.setName("Java Test Organisation");
Organisation newOrganisation = this.MySDK.organisation.create(organisation);
this.testOrganisation(newOrganisation);
this.newOrganisation = newOrganisation;
this.testUpdateOrganisation();
}
public void testUpdateOrganisation() throws RequestException
{
// I tried setting ID to null, but that doesn't work either
// even though I've set Gson to not serialise null values
this.newOrganisation.setId(null);
this.newOrganisation.setName(this.newName);
// For debugging
System.out.println(this.newOrganisation.toJson());
Organisation updatedOrganisation = this.MySDK.organisation.update(this.newOrganisation.getId(), this.newOrganisation);
this.testOrganisation(updatedOrganisation);
assertEquals(newOrganisation.getName(), this.newName);
this.testDeleteOrganisation();
}
Can anyone spot what I'm doing wrong? I have a feeling it has something to do with the fact that the instance already has/had a value for ID, but that shouldn't matter if I explicity tell it not to serialise it right?
Thanks in advance for the help.
EDIT: At this.MySDK.organisation.update(this.newOrganisation.getId(), this.newOrganisation);
, that does not edit the organisation instance. The given ID is merely added to the URL that the SDK will POST to (POST /organisation/{id}
)
Gson gson = new GsonBuilder() . excludeFieldsWithoutExposeAnnotation() . create(); String jsonString = gson. toJson(source); assertEquals(expectedResult, jsonString);
@SerializeName is used to set the key that json object will include ,however @Expose is used to decide whether the variable will be exposed for Serialisation and Deserialisation ,or not.
3. Deserialize JSON With Extra Unknown Fields to Object. As you can see, Gson will ignore the unknown fields and simply match the fields that it's able to.
In this example you'll see how the Gson library handles the object fields. For object fields to be serialized into JSON string it doesn't need to use any annotations, it can even read private fields.
serialize – If true, the field marked with this annotation is written out in the JSON while serializing. deserialize – If true, the field marked with this annotation is deserialized from the JSON. 1.2. Creating Gson instance
By default, Gson would exclude a field from serialization and deserialization – both, if we simply mark the field as transient. Remember that it is not capable of blocking one way transformation.
The fields that are marked with the @Expose annotation will be included in the JSON representation. So the fields that are not annotated e.g.: “id, firstName, lastName” will not be serialized into the JSON object. When creating your JSON object you must create Gson by using new GsonBuilder ().excludeFieldsWithoutExposeAnnotation ().create ().
It means that Gson will exclude all fields in a class that are not marked with @Expose annotation. The @Expose annotation is useful in a style of programming where you want to explicitly specify all fields that should get considered for serialization or deserialization.
As you mentioned in your comments, @Expose
should be the better choice over transient
here. It's important to note that the default Gson instance does not regard the @Expose
annotation! It'll simply ignore it, no matter what you set as option.
If you want to activate the @Expose
options, you need to customize Gson. Based on your code above, change it to:
public static final Gson PRETTY_PRINT_JSON = new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation();
.create();
Your @Expose(serialize = false)
should be active and excluded during serialization.
Thanks to @peitek for pointing out that @Expose
is ignored unless .excludeFieldsWithoutExposeAnnotation()
is added to the GsonBuilder()
. However, I choose not to go with this route as it would require me to add @Expose
to every single parameter of my model classes just to ignore one field on serialisation. Instead I wrote a ExclusionStrategy
that checks for the presence of a custom SkipSerialisation
annotation on the parameter. I implemented these as follows:
The full GsonBuilder
with the strategy:
public static final Gson PRETTY_PRINT_JSON = new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy()
{
@Override
public boolean shouldSkipField(FieldAttributes f)
{
return f.getAnnotation(SkipSerialisation.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> clazz)
{
return false;
}
})
.setPrettyPrinting()
.create();
And the annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SkipSerialisation
{
}
Now I can just do
@SkipSerialisation
@SerializedName("_id")
private String id;
and it works!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With