Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson: parameter get serialised even though it has @Expose(serialize = false)

Tags:

java

json

gson

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

like image 843
Roemer Avatar asked Mar 27 '17 13:03

Roemer


People also ask

How to exclude field in Gson?

Gson gson = new GsonBuilder() . excludeFieldsWithoutExposeAnnotation() . create(); String jsonString = gson. toJson(source); assertEquals(expectedResult, jsonString);

What is the use of @expose in Gson?

@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.

Does Gson ignore extra fields?

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.

Does Gson serialize private fields?

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.

What is the difference between deserialize and serialize in Gson?

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

How to exclude a field from serialization in Gson?

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.

Which fields are not serialized into the JSON object?

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 ().

What is the use of @expose in Gson?

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.


2 Answers

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.

like image 129
peitek Avatar answered Oct 12 '22 14:10

peitek


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!

like image 27
Roemer Avatar answered Oct 12 '22 13:10

Roemer