Core Java

Gson @Expose vs @SerializedName

1. Introduction

Gson @Expose and @SerializedName annotations are provided by the Gson library to control serialization and deserialization behavior when converting an object to JSON and vice versa. They serve different purposes but can be used together. I hope you enjoy our Gson @Exposed vs @SerializedName article.

Here are the @Expose and @SerializedName definitions from the com.google.gson.annotations package.

@Expose

/*An annotation that indicates this member should be exposed for JSON serialization or deserialization.
This annotation has no effect unless you build Gson with a GsonBuilder and invoke GsonBuilder.excludeFieldsWithoutExposeAnnotation() method.*/ 
@Documented
@Retention(RUNTIME)
@Target(FIELD)
public @interface Expose

@SerializedName

/*An annotation that indicates this member should be serialized to JSON with the provided name value as its field name. 
This annotation will override any FieldNamingPolicy setting.*/
@Documented
@Retention(RUNTIME)
@Target({FIELD,METHOD})
public @interface SerializedName
  • Line 5: it defines both FIELD and METHOD, but Out-of-box Gson ignores method-level annotations. it uses field-based reflection today.

2. Setup

In this step, I will create a Gradle project along with Lombok and Gson libraries.

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.5.0'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'org.jcg.zheng'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	
	// https://mvnrepository.com/artifact/com.google.code.gson/gson
	implementation("com.google.code.gson:gson:2.13.1")
}

tasks.named('test') {
	useJUnitPlatform()
}
  • Line 34: include the Gson library.

3. POJO With Expose and Serializedname Annotations

In this step, I will create a SomePojo.java that annotates with both @Expose and @SerializedName annotations.

SomePojo.java

package org.jcg.zheng.gsondemo.data;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class SomePojo {

	private String defaultName;

	@SerializedName("exposedChangedName4")
	@Expose(serialize = true, deserialize = true)
	private String exposedChangedName;

	@Expose(serialize = true, deserialize = true)
	private String exposedName;

	@SerializedName(value = "jsonName")
	private String javaName;

	@SerializedName(value = "name1", alternate = { "name2", "name3" })
	String name;

	@Expose(serialize = false, deserialize = false)
	private String notExposedName;

}
  • Line 15, 16: the exposedChangedName field is annotated with both @Expose and @SerializedName. So serializes/deserializes the exposedChangedName field with the JSON key “exposedChangedName4” and ensures that the serialize = true, deserialize = true setting ensures that it is included if using excludeFieldsWithoutExposeAnnotation().
  • Line 19: the exposedName field is included when using excludeFieldsWithoutExposeAnnotation() as it’s annotated with @Expose(serialize = true, deserialize = true.
  • Line 22: map javaName to jsonName via @SerializedName(value = "jsonName").
  • Line 25: map name to “name1“, “name2“, or “name3” via @SerializedName‘s alternate attribute. When deserializing from JSON to POJO, any fields of “name1“, “name2“, or “name3” can be mapped to the name. The last one in the JSON will be used if many similar fields exist.
  • Line 28: the notExposedName is not included as both serialize and deserialize attributes are set to false when using excludeFieldsWithoutExposeAnnotation().

4. GsonConverter

In this step, I will create a GsonConverter.java that converts a SomePojo object to JSON String and vice versa.

GsonConverter.java

package org.jcg.zheng.gsondemo.converter;

import org.jcg.zheng.gsondemo.data.SomePojo;

import com.google.gson.Gson;

public class GsonConverter {

	public String toJson(final Gson gson, final SomePojo obj) {
		return gson.toJson(obj);
	}

	public SomePojo fromJson(final Gson gson, final String jsonString) {
		return gson.fromJson(jsonString, SomePojo.class);
	}
}

5. Test for Expose and Serializedname Annotations

In this step, I will create a GsonConverterTest.java class to demonstrate how @Expose and @SerializedName annotations affected the JSON serialization and deserialization.

GsonConverterTest.java

package org.jcg.zheng.gsondemo.converter;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import org.jcg.zheng.gsondemo.data.SomePojo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

class GsonConverterTest {

	private Gson gson;
	private Gson gsonWithExpose;
	private GsonConverter testClass = new GsonConverter();
	private SomePojo testObj;

	@BeforeEach
	void setup() {
		testObj = SomePojo.builder().defaultName("Mary").javaName("zheng").exposedName("public")
				.notExposedName("private").name("MaryZheng").name("someName").exposedChangedName("Sammo").build();

		gson = new Gson();
		gsonWithExpose = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
	}

	@Test
	void test_from_to_Json_via_gson() {
		String jsonStr = testClass.toJson(gson, testObj);
		System.out.println("test_from_to_Json_via_gson " + jsonStr);
		assertEquals(
				"{\"defaultName\":\"Mary\",\"exposedChangedName4\":\"Sammo\",\"exposedName\":\"public\",\"jsonName\":\"zheng\",\"name1\":\"someName\",\"notExposedName\":\"private\"}",
				jsonStr);

		SomePojo convertedObj = testClass.fromJson(gson, jsonStr);

		assertEquals("Mary", convertedObj.getDefaultName());
		assertEquals("zheng", convertedObj.getJavaName());
		assertEquals("public", convertedObj.getExposedName());
		assertEquals("private", convertedObj.getNotExposedName());
		assertEquals("someName", convertedObj.getName());
		assertEquals("Sammo", convertedObj.getExposedChangedName());
	}

	@Test
	void test_from_to_Json_via_gsonWithExpose() {
		String jsonStr = testClass.toJson(gsonWithExpose, testObj);
		System.out.println("test_from_to_Json_via_gsonWithExpose" + jsonStr);
		assertEquals("{\"exposedChangedName4\":\"Sammo\",\"exposedName\":\"public\"}", jsonStr);

		SomePojo convertedObj = testClass.fromJson(gsonWithExpose, jsonStr);

		assertNull(convertedObj.getDefaultName());
		assertNull(convertedObj.getJavaName());
		assertNull(convertedObj.getNotExposedName());
		assertNull(convertedObj.getName());
		assertEquals("public", convertedObj.getExposedName());
		assertEquals("Sammo", convertedObj.getExposedChangedName());
	}

	@Test
	void test_gson_FieldNamingPolicy_case() {
		Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();

		String jsonStr = testClass.toJson(gson, testObj);
		System.out.println("test_gson_FieldNamingPolicy_case" + jsonStr);
		assertEquals(
				"{\"DefaultName\":\"Mary\",\"exposedChangedName4\":\"Sammo\",\"ExposedName\":\"public\",\"jsonName\":\"zheng\",\"name1\":\"someName\",\"NotExposedName\":\"private\"}",
				jsonStr);
	}

	@Test
	void test_gson_FieldNamingPolicy_dash() {
		Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES).create();

		String jsonStr = testClass.toJson(gson, testObj);
		System.out.println("test_gson_FieldNamingPolicy_dash" + jsonStr);
		assertEquals(
				"{\"default-name\":\"Mary\",\"exposedChangedName4\":\"Sammo\",\"exposed-name\":\"public\",\"jsonName\":\"zheng\",\"name1\":\"someName\",\"not-exposed-name\":\"private\"}",
				jsonStr);
	}

	@Test
	void test_gson_FieldNamingPolicy_dot() {
		Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DOTS).create();

		String jsonStr = testClass.toJson(gson, testObj);
		System.out.println("test_gson_FieldNamingPolicy_dot" + jsonStr);
		assertEquals(
				"{\"default.name\":\"Mary\",\"exposedChangedName4\":\"Sammo\",\"exposed.name\":\"public\",\"jsonName\":\"zheng\",\"name1\":\"someName\",\"not.exposed.name\":\"private\"}",
				jsonStr);
	}

	@Test
	void test_gson_FieldNamingPolicy_space() {
		Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES).create();

		String jsonStr = testClass.toJson(gson, testObj);
		System.out.println("test_gson_FieldNamingPolicy_space" + jsonStr);
		assertEquals(
				"{\"Default Name\":\"Mary\",\"exposedChangedName4\":\"Sammo\",\"Exposed Name\":\"public\",\"jsonName\":\"zheng\",\"name1\":\"someName\",\"Not Exposed Name\":\"private\"}",
				jsonStr);
	}

	@Test
	void test_gson_FieldNamingPolicy_underscore() {
		Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();

		String jsonStr = testClass.toJson(gson, testObj);
		System.out.println("test_gson_FieldNamingPolicy_underscore" + jsonStr);
		assertEquals(
				"{\"default_name\":\"Mary\",\"exposedChangedName4\":\"Sammo\",\"exposed_name\":\"public\",\"jsonName\":\"zheng\",\"name1\":\"someName\",\"not_exposed_name\":\"private\"}",
				jsonStr);

	}

	@Test
	void test_serializedname_alternate_gson() {
		String testJson = "{'name1':'v1'}";
		SomePojo target = testClass.fromJson(gson, testJson);
		assertEquals("v1", target.getName());

		testJson = "{'name2':'v2'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v2", target.getName());

		testJson = "{'name3':'v3'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v3", target.getName());

		//the last one in JSON will be used
		testJson = "{'name1':'v1', 'name2':'v2'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v2", target.getName());
		
		testJson = "{'name2':'v2', 'name1':'v1'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v1", target.getName());
		
		testJson = "{'name1':'v1', 'name3':'v3'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v3", target.getName());
		
		testJson = "{'name1':'v1', 'name2':'v2', 'name3':'v3'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v3", target.getName());
		
		testJson = "{'name2':'v2', 'name3':'v3'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v3", target.getName());
		
		testJson = "{'name3':'v3', 'name2':'v2'}";
		target = testClass.fromJson(gson, testJson);
		assertEquals("v2", target.getName());

	}

	@Test
	void test_serializedname_alternate_gsonWithExpose() {
		String testJson = "{\"Default Name\":\"Mary\",\"exposedChangedName4\":\"Sammo\",\"Exposed Name\":\"public\",\"jsonName\":\"zheng\",\"name1\":\"someName\",\"Not Exposed Name\":\"private\"}";
		SomePojo target = testClass.fromJson(gsonWithExpose, testJson);
		assertNull(target.getName());
		assertEquals("Sammo", target.getExposedChangedName());

		testJson = "{'name2':'v2'}";
		target = testClass.fromJson(gsonWithExpose, testJson);
		assertNull(target.getName());

		testJson = "{'name3':'v3'}";
		target = testClass.fromJson(gsonWithExpose, testJson);
		assertNull(target.getName());
	}

}
  • Line 23: the testObj object sets all the fields. You will see the serialized JSONs are different between the default gson and gsonWithExpose objects.
  • Line 26: create a gson object with the default setting. It includes all the fields during the serialization and deserialization.
  • Line 27: create a customized gsonWithExpose object via excludeFieldsWithoutExposeAnnotation. It only processes these fields annotated with @Expose annotation.
  • Line 31: test_from_to_Json_via_gson verifies the default gson’s serialization and deserialization work as expected. All fields’ data are mapped as lines 40-45 show.
  • Line 49: test_from_to_Json_via_gsonWithExpose verified the customized gsonWithExpose with excludeFieldsWithoutExposeAnnotation only serializes and deserializes these fields marked with @Expose annotation as lines 55-62 show.
  • Line 65, 76, 87, 98, 109: verified @SerializedName overwrites the FieldNamingPolicy setting. You can confirm by viewing the JSON string in the test results.
  • Line 121: test_serializedname_alternate_gson verifies that name1, name2, and name3 map to the name field because of the alternate attribute setting when using the gson object. It also confirms that the last one in the JSON will be mapped to name if name1, name2, and name3 co-exist in JSON.
  • Line 161: test_serializedname_alternate_gsonWithExpose is used to compare with test_serializedname_alternate_gson. It is not mapped with gsonWithExpose because the name field is not annotated with @Expose.

Run the Junit tests and capture the results.

Junit Test Output

test_gson_FieldNamingPolicy_case{"DefaultName":"Mary","exposedChangedName4":"Sammo","ExposedName":"public","jsonName":"zheng","name1":"someName","NotExposedName":"private"}
test_gson_FieldNamingPolicy_dash{"default-name":"Mary","exposedChangedName4":"Sammo","exposed-name":"public","jsonName":"zheng","name1":"someName","not-exposed-name":"private"}
test_gson_FieldNamingPolicy_dot{"default.name":"Mary","exposedChangedName4":"Sammo","exposed.name":"public","jsonName":"zheng","name1":"someName","not.exposed.name":"private"}
test_gson_FieldNamingPolicy_underscore{"default_name":"Mary","exposedChangedName4":"Sammo","exposed_name":"public","jsonName":"zheng","name1":"someName","not_exposed_name":"private"}
test_from_to_Json_via_gson {"defaultName":"Mary","exposedChangedName4":"Sammo","exposedName":"public","jsonName":"zheng","name1":"someName","notExposedName":"private"}
test_gson_FieldNamingPolicy_space{"Default Name":"Mary","exposedChangedName4":"Sammo","Exposed Name":"public","jsonName":"zheng","name1":"someName","Not Exposed Name":"private"}
test_from_to_Json_via_gsonWithExpose{"exposedChangedName4":"Sammo","exposedName":"public"}
  • the fields: name1, exposedChangedName4, and jsonName remain unchanged when FieldNamingPolicy changed because they are marked with @SerializedName.
gson expose and serializedname annotations
Figure 1 Test Results

6. Conclusion

In this example, I explained both @Expose and @SerializedName annotations and demonstrated with Junit tests. The @Expose annotation’s purpose is to control whether a field should be included in serialization or deserialization. By default, Gson includes all fields, but if configured with the @Expose annotation, then only fields with @Expose annotation will be processed. The @SerializedName annotation’s purpose is to specify the name to be used. Table 1 outlines the summary of both annotations.

AnnotationPurposeAttributesNeed a Special Config?Usage Example
@Exposeinclude/exclude fields in JSON processingserialize and deserializeYes, with excludeFieldsWithoutExposeAnnotation()skipping sensitive data
@SerializedNamerename field for Json key mapping and overwrite the FieldNamingPolicyvalue and alternateNomapping “userId" to “user_id
Table 1 @Expose and @SerializedName Summary

7. Download

This was an example of a Gradle project which included Gson expose and serializedname annotations examples.

Download
You can download the full source code of this example here: Expose and SerializedName Annotations in Gson

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button