Read-only field in GSON serialization [duplicate] - java

I'm trying to learn Gson and I'm struggling with field exclusion. Here are my classes
public class Student {
private Long id;
private String firstName = "Philip";
private String middleName = "J.";
private String initials = "P.F";
private String lastName = "Fry";
private Country country;
private Country countryOfBirth;
}
public class Country {
private Long id;
private String name;
private Object other;
}
I can use the GsonBuilder and add an ExclusionStrategy for a field name like firstName or country but I can't seem to manage to exclude properties of certain fields like country.name.
Using the method public boolean shouldSkipField(FieldAttributes fa), FieldAttributes doesn't contain enough information to match the field with a filter like country.name.
P.S: I want to avoid annotations since I want to improve on this and use RegEx to filter fields out.
Edit: I'm trying to see if it's possible to emulate the behavior of Struts2 JSON plugin
using Gson
<interceptor-ref name="json">
<param name="enableSMD">true</param>
<param name="excludeProperties">
login.password,
studentList.*\.sin
</param>
</interceptor-ref>
Edit:
I reopened the question with the following addition:
I added a second field with the same type to futher clarify this problem. Basically I want to exclude country.name but not countrOfBirth.name. I also don't want to exclude Country as a type.
So the types are the same it's the actual place in the object graph that I want to pinpoint and exclude.

Any fields you don't want serialized in general you should use the "transient" modifier, and this also applies to json serializers (at least it does to a few that I have used, including gson).
If you don't want name to show up in the serialized json give it a transient keyword, eg:
private transient String name;
More details in the Gson documentation

Nishant provided a good solution, but there's an easier way. Simply mark the desired fields with the #Expose annotation, such as:
#Expose private Long id;
Leave out any fields that you do not want to serialize. Then just create your Gson object this way:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

So, you want to exclude firstName and country.name. Here is what your ExclusionStrategy should look like
public class TestExclStrat implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> arg0) {
return false;
}
public boolean shouldSkipField(FieldAttributes f) {
return (f.getDeclaringClass() == Student.class && f.getName().equals("firstName"))||
(f.getDeclaringClass() == Country.class && f.getName().equals("name"));
}
}
If you see closely it returns true for Student.firstName and Country.name, which is what you want to exclude.
You need to apply this ExclusionStrategy like this,
Gson gson = new GsonBuilder()
.setExclusionStrategies(new TestExclStrat())
//.serializeNulls() <-- uncomment to serialize NULL fields as well
.create();
Student src = new Student();
String json = gson.toJson(src);
System.out.println(json);
This returns:
{ "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91}}
I assume the country object is initialized with id = 91L in student class.
You may get fancy. For example, you do not want to serialize any field that contains "name" string in its name. Do this:
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().toLowerCase().contains("name");
}
This will return:
{ "initials": "P.F", "country": { "id": 91 }}
EDIT: Added more info as requested.
This ExclusionStrategy will do the thing, but you need to pass "Fully Qualified Field Name". See below:
public class TestExclStrat implements ExclusionStrategy {
private Class<?> c;
private String fieldName;
public TestExclStrat(String fqfn) throws SecurityException, NoSuchFieldException, ClassNotFoundException
{
this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
this.fieldName = fqfn.substring(fqfn.lastIndexOf(".")+1);
}
public boolean shouldSkipClass(Class<?> arg0) {
return false;
}
public boolean shouldSkipField(FieldAttributes f) {
return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
}
}
Here is how we can use it generically.
Gson gson = new GsonBuilder()
.setExclusionStrategies(new TestExclStrat("in.naishe.test.Country.name"))
//.serializeNulls()
.create();
Student src = new Student();
String json = gson.toJson(src);
System.out.println(json);
It returns:
{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}

After reading all available answers I found out, that most flexible, in my case, was to use custom #Exclude annotation. So, I implemented simple strategy for this (I didn't want to mark all fields using #Expose nor I wanted to use transient which conflicted with in app Serializable serialization) :
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Exclude {
}
Strategy:
public class AnnotationExclusionStrategy implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(Exclude.class) != null;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
Usage:
new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();

I ran into this issue, in which I had a small number of fields I wanted to exclude only from serialization, so I developed a fairly simple solution that uses Gson's #Expose annotation with custom exclusion strategies.
The only built-in way to use #Expose is by setting GsonBuilder.excludeFieldsWithoutExposeAnnotation(), but as the name indicates, fields without an explicit #Expose are ignored. As I only had a few fields I wanted to exclude, I found the prospect of adding the annotation to every field very cumbersome.
I effectively wanted the inverse, in which everything was included unless I explicitly used #Expose to exclude it. I used the following exclusion strategies to accomplish this:
new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
final Expose expose = fieldAttributes.getAnnotation(Expose.class);
return expose != null && !expose.serialize();
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.addDeserializationExclusionStrategy(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
final Expose expose = fieldAttributes.getAnnotation(Expose.class);
return expose != null && !expose.deserialize();
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.create();
Now I can easily exclude a few fields with #Expose(serialize = false) or #Expose(deserialize = false) annotations (note that the default value for both #Expose attributes is true). You can of course use #Expose(serialize = false, deserialize = false), but that is more concisely accomplished by declaring the field transient instead (which does still take effect with these custom exclusion strategies).

You can explore the json tree with gson.
Try something like this :
gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");
You can add some properties also :
gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);
Tested with gson 2.2.4.

I came up with a class factory to support this functionality. Pass in any combination of either fields or classes you want to exclude.
public class GsonFactory {
public static Gson build(final List<String> fieldExclusions, final List<Class<?>> classExclusions) {
GsonBuilder b = new GsonBuilder();
b.addSerializationExclusionStrategy(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return fieldExclusions == null ? false : fieldExclusions.contains(f.getName());
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return classExclusions == null ? false : classExclusions.contains(clazz);
}
});
return b.create();
}
}
To use, create two lists (each is optional), and create your GSON object:
static {
List<String> fieldExclusions = new ArrayList<String>();
fieldExclusions.add("id");
fieldExclusions.add("provider");
fieldExclusions.add("products");
List<Class<?>> classExclusions = new ArrayList<Class<?>>();
classExclusions.add(Product.class);
GSON = GsonFactory.build(null, classExclusions);
}
private static final Gson GSON;
public String getSomeJson(){
List<Provider> list = getEntitiesFromDatabase();
return GSON.toJson(list);
}

I solved this problem with custom annotations.
This is my "SkipSerialisation" Annotation class:
#Target (ElementType.FIELD)
public #interface SkipSerialisation {
}
and this is my GsonBuilder:
gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {
#Override public boolean shouldSkipField (FieldAttributes f) {
return f.getAnnotation(SkipSerialisation.class) != null;
}
#Override public boolean shouldSkipClass (Class<?> clazz) {
return false;
}
});
Example :
public class User implements Serializable {
public String firstName;
public String lastName;
#SkipSerialisation
public String email;
}

Kotlin's #Transientannotation also does the trick apparently.
data class Json(
#field:SerializedName("serialized_field_1") val field1: String,
#field:SerializedName("serialized_field_2") val field2: String,
#Transient val field3: String
)
Output:
{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}

Or can say whats fields not will expose with:
Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
on your class on attribute:
private **transient** boolean nameAttribute;

I used this strategy:
i excluded every field which is not marked with #SerializedName annotation, i.e.:
public class Dummy {
#SerializedName("VisibleValue")
final String visibleValue;
final String hiddenValue;
public Dummy(String visibleValue, String hiddenValue) {
this.visibleValue = visibleValue;
this.hiddenValue = hiddenValue;
}
}
public class SerializedNameOnlyStrategy implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(SerializedName.class) == null;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
Gson gson = new GsonBuilder()
.setExclusionStrategies(new SerializedNameOnlyStrategy())
.create();
Dummy dummy = new Dummy("I will see this","I will not see this");
String json = gson.toJson(dummy);
It returns
{"VisibleValue":"I will see this"}

Another approach (especially useful if you need to make a decision to exclude a field at runtime) is to register a TypeAdapter with your gson instance. Example below:
Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())
In the case below, the server would expect one of two values but since they were both ints then gson would serialize them both. My goal was to omit any value that is zero (or less) from the json that is posted to the server.
public class BloodPressurePostSerializer implements JsonSerializer<BloodPressurePost> {
#Override
public JsonElement serialize(BloodPressurePost src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
if (src.systolic > 0) {
jsonObject.addProperty("systolic", src.systolic);
}
if (src.diastolic > 0) {
jsonObject.addProperty("diastolic", src.diastolic);
}
jsonObject.addProperty("units", src.units);
return jsonObject;
}
}

I'm working just by putting the #Expose annotation, here my version that I use
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
In Model class:
#Expose
int number;
public class AdapterRestApi {
In the Adapter class:
public EndPointsApi connectRestApi() {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(90000, TimeUnit.SECONDS)
.readTimeout(90000,TimeUnit.SECONDS).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ConstantRestApi.ROOT_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit.create (EndPointsApi.class);
}

I have Kotlin version
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD)
internal annotation class JsonSkip
class SkipFieldsStrategy : ExclusionStrategy {
override fun shouldSkipClass(clazz: Class<*>): Boolean {
return false
}
override fun shouldSkipField(f: FieldAttributes): Boolean {
return f.getAnnotation(JsonSkip::class.java) != null
}
}
and how You can add this to Retrofit GSONConverterFactory:
val gson = GsonBuilder()
.setExclusionStrategies(SkipFieldsStrategy())
//.serializeNulls()
//.setDateFormat(DateFormat.LONG)
//.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
//.setPrettyPrinting()
//.registerTypeAdapter(Id.class, IdTypeAdapter())
.create()
return GsonConverterFactory.create(gson)

This what I always use:
The default behaviour implemented in Gson is that null object fields are ignored.
Means Gson object does not serialize fields with null values to JSON. If a field in a Java object is null, Gson excludes it.
You can use this function to convert some object to null or well set by your own
/**
* convert object to json
*/
public String toJson(Object obj) {
// Convert emtpy string and objects to null so we don't serialze them
setEmtpyStringsAndObjectsToNull(obj);
return gson.toJson(obj);
}
/**
* Sets all empty strings and objects (all fields null) including sets to null.
*
* #param obj any object
*/
public void setEmtpyStringsAndObjectsToNull(Object obj) {
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
Object fieldObj = field.get(obj);
if (fieldObj != null) {
Class fieldType = field.getType();
if (fieldType.isAssignableFrom(String.class)) {
if(fieldObj.equals("")) {
field.set(obj, null);
}
} else if (fieldType.isAssignableFrom(Set.class)) {
for (Object item : (Set) fieldObj) {
setEmtpyStringsAndObjectsToNull(item);
}
boolean setFielToNull = true;
for (Object item : (Set) field.get(obj)) {
if(item != null) {
setFielToNull = false;
break;
}
}
if(setFielToNull) {
setFieldToNull(obj, field);
}
} else if (!isPrimitiveOrWrapper(fieldType)) {
setEmtpyStringsAndObjectsToNull(fieldObj);
boolean setFielToNull = true;
for (Field f : fieldObj.getClass().getDeclaredFields()) {
f.setAccessible(true);
if(f.get(fieldObj) != null) {
setFielToNull = false;
break;
}
}
if(setFielToNull) {
setFieldToNull(obj, field);
}
}
}
} catch (IllegalAccessException e) {
System.err.println("Error while setting empty string or object to null: " + e.getMessage());
}
}
}
private void setFieldToNull(Object obj, Field field) throws IllegalAccessException {
if(!Modifier.isFinal(field.getModifiers())) {
field.set(obj, null);
}
}
private boolean isPrimitiveOrWrapper(Class fieldType) {
return fieldType.isPrimitive()
|| fieldType.isAssignableFrom(Integer.class)
|| fieldType.isAssignableFrom(Boolean.class)
|| fieldType.isAssignableFrom(Byte.class)
|| fieldType.isAssignableFrom(Character.class)
|| fieldType.isAssignableFrom(Float.class)
|| fieldType.isAssignableFrom(Long.class)
|| fieldType.isAssignableFrom(Double.class)
|| fieldType.isAssignableFrom(Short.class);
}

in kotlin can use #Transient to ignore the field... eg.
data class MyClass{
#Transient var myVar: Boolean
//....
}

Use different DTO for cached object.
For example, you can create UserCached class and keep there only fields you need.
After that, create mapper to map objects back & forth. Mapstruct is good for that.
Such approach solves the problem, decouples your application, and makes changes in your primary DTO more safe to make.

Related

Jackson Deserialization not calling deserialize on Custom Deserializer

I want to deserialize classes of the form:
public class TestFieldEncryptedMessage implements ITextMessage {
#JsonProperty("text")
#Encrypted(cipherAlias = "testAlias")
private String text;
public TestFieldEncryptedMessage() {
}
#JsonCreator
public TestFieldEncryptedMessage(#JsonProperty("text") String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
Where the text is encrypted and deserialization should unencrypt the value before rebuilding the TestFieldEncryptedMessage instance.
I am following an approach very similar to: https://github.com/codesqueak/jackson-json-crypto
That is, I am building a module extending SimpleModule:
public class CryptoModule extends SimpleModule {
public final static String GROUP_ID = "au.com.auspost.messaging";
public final static String ARTIFACT_ID = "jackson-json-crypto";
private EncryptedSerializerModifier serializerModifier;
private EncryptedDeserializerModifier deserializerModifier;
public CryptoModule() {
}
public CryptoModule addEncryptionService(final EncryptionService encryptionService) {
serializerModifier = new EncryptedSerializerModifier(encryptionService);
deserializerModifier = new EncryptedDeserializerModifier(encryptionService);
return this;
}
#Override
public String getModuleName() {
return ARTIFACT_ID;
}
#Override
public Version version() {
return new Version(major, minor, patch, null, GROUP_ID, ARTIFACT_ID);
}
#Override
public void setupModule(final SetupContext context) {
if ((null == serializerModifier) || (null == deserializerModifier))
throw new EncryptionException("Crypto module not initialised with an encryption service");
context.addBeanSerializerModifier(serializerModifier);
context.addBeanDeserializerModifier(deserializerModifier);
}
}
As you can see, two modifiers are set up: the EncryptedSerializerModifier works perfectly and is called by the ObjectMapper, but the deserializer behind the EncryptedDeserializerModifier is ignored.
As is seen in many other examples on SO such as here: How can I include raw JSON in an object using Jackson?, I set up the EncryptedDeserializerModifier with:
public class EncryptedDeserializerModifier extends BeanDeserializerModifier {
private final EncryptionService encryptionService;
private Map<String, SettableBeanProperty> properties = new HashMap<>();
public EncryptedDeserializerModifier(final EncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
#Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config, final BeanDescription beanDescription, final BeanDeserializerBuilder builder) {
Encrypted annotation = beanDescription.getType().getRawClass().getAnnotation(Encrypted.class);
Iterator it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty p = (SettableBeanProperty) it.next();
if (null != p.getAnnotation(Encrypted.class)) {
JsonDeserializer<Object> current = p.getValueDeserializer();
properties.put(p.getName(), p);
builder.addOrReplaceProperty(p.withValueDeserializer(new EncryptedJsonDeserializer(encryptionService, current, p)), true);
}
}
return builder;
}
}
Finally, the EncryptedJsonDeserializer itself overrides the following:
#Override
public Object deserialize(final JsonParser parser, final DeserializationContext context) throws JsonMappingException {
JsonDeserializer<?> deserializer = baseDeserializer;
if (deserializer instanceof ContextualDeserializer) {
deserializer = ((ContextualDeserializer) deserializer).createContextual(context, property);
}
return service.decrypt(parser, deserializer, context, property != null ? property.getType() : type);
}
#Override
public JsonDeserializer<?> createContextual(final DeserializationContext context, final BeanProperty property) throws JsonMappingException {
JsonDeserializer<?> wrapped = context.findRootValueDeserializer(property.getType());
return new EncryptedJsonDeserializer(service, wrapped, property);
}
The createContextual() method is called, but the deserialize method is not called. The property throughout the execution is always the "text" property, so I seem to have the right context.
anyone know why the ObjectMapper doesn't find the right Deserializer?
EDIT added implements ITextMessage to decrypted class, which I thought was an unimportant detail, but turned out to be the cause of the issue.
I found the issue! If you look closely at the TestFieldEncryptedMessage class, whose text field is encrypted, you can see that it implements an interface. The interface is used so that the messages give some extra tooling for asserts in tests, however for deserialization, there is an unintended consequence. When the ObjectMapper is working its way through the json string, it tries, I think, to match a deserializer to a field inside ITextMessage, not to a field inside TestFieldEncryptedMessage, which is why the custom deserializer was not called (there is no text field in ITextMessage).
Once I stopped implementing ITextMessage, the custom deserializer was called.

Deserialize mutable (primitive/object) JSON property to object with Jackson

Given a JSON object having a mutable property (e.g. label) which can be either primitive value (e.g. string) or an object. A hypothetical use-case could be a wrapper for pluralized translation of a label:
{
"label": "User name"
}
or
{
"label": {
"one": "A label",
"other": "The labels"
}
}
The goal is to bring Jackson deserialization always return a fixed structure on the Java-side. Thus, if a primitive value is given it is always translated to a certain property (e.g. other) of the target POJO, i.e.:
public class Translations {
#JsonDeserialize(using = PluralizedTranslationDeserializer.class)
public PluralizedTranslation label;
}
public class PluralizedTranslation {
public String one;
public String other; // used as default fields for primitive value
}
Currently the issue is solved by using a custom JsonDeserializer which checks whether the property is primitive or not:
public class PluralizedTranslationDeserializer extends JsonDeserializer {
#Override
public PluralizedTranslation deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
PluralizedTranslation translation;
if (node.isTextual()) {
translation = new PluralizedTranslation();
translation.other = node.asText();
} else {
translation = oc.treeToValue(node, PluralizedTranslation.class);
}
return translation;
}
}
Is there a more elegant approach for handling mutable JSON properties without having a decoder which operates on node level?
You could make the label setter more generic and add some logic handling the two cases.
public class Translations {
// Fields omitted.
#JsonProperty("label")
public void setLabel(Object o) {
if (o instanceof String) {
// Handle the first case
} else if (o instanceof Map) {
// Handle the second case
} else {
throw new RuntimeException("Unsupported");
}
}
}
Alternative solution, which places the factory method inside the PluralizedTranslation class, leaving the Translations class unaffected:
public class PluralizedTranslation {
public String one;
public String other; // used as default fields for primitive value
#JsonCreator
private PluralizedTranslation(Object obj) {
if (obj instanceof Map) {
Map map = (Map) obj;
one = (String) map.get("one");
other = (String) map.get("other");
} else if (obj instanceof String) {
other = (String) obj;
} else {
throw new RuntimeException("Unsupported");
}
}
}
Note that the constructor can be marked as private to prevent unintended usage.

Gson Serialize Circular References Using Stubs

I'm trying to implement some simple Json serialization functionality but I'm having a hard time coping with the massive complexity of Gson.
So basically I have a bunch of Entity classes which reference each other with a lot of circular reference. To serialize this structure to JSON I want to keep track of the objects already serialized. The Entity classes all implement an interface called Identified which has one method String getId() giving a globally unique id. So during serializiation of one root element, I want to store all encountered ids in a Set and decide based on that set, whether to fully serialize an object or to serialize that object as a stub
"something": {
"__stub": "true",
"id": "..."
}
This shouldn't be too hard a task in my opinion, but I haven't been able to put something together. Using a custom JsonSerializer I'm not able to have an object (that is not to be serialized as a stub) serialized in the default way. Using a TypeAdapterFactory, I'm not able to access the actual object.
So, any help on how to achieve this, would be very nice!
Best regards
I'm not sure if it's possible easily. As far as I know, Gson promotes immutability and seems to lack custom serialization context support (at least I don't know if it's possible to use custom JsonSerializationContext wherever possible). Thus, one of possible work-around might be the following:
IIdentifiable.java
A simple interface to request a custom ID for an object.
interface IIdentifiable<ID> {
ID getId();
}
Entity.java
A simple entity that can hold another entity references in two manners:
a direct dependency to a "next" entity;
a collection of references to other references.
final class Entity
implements IIdentifiable<String> {
#SerializedName(ID_PROPERTY_NAME)
private final String id;
private final Collection<Entity> entities = new ArrayList<>();
private Entity next;
private Entity(final String id) {
this.id = id;
}
static Entity entity(final String id) {
return new Entity(id);
}
#Override
public String getId() {
return id;
}
Entity setAll(final Entity... entities) {
this.entities.clear();
this.entities.addAll(asList(entities));
return this;
}
Entity setNext(final Entity next) {
this.next = next;
return this;
}
}
IdentitySerializingTypeAdapterFactory.java
I didn't find any easier way rather than making it a type adapter factory, and, unfortunately, this implementation is totally stateful and cannot be reused.
final class IdentitySerializingTypeAdapterFactory
implements TypeAdapterFactory {
private final Collection<Object> traversedEntityIds = new HashSet<>();
private IdentitySerializingTypeAdapterFactory() {
}
static TypeAdapterFactory identitySerializingTypeAdapterFactory() {
return new IdentitySerializingTypeAdapterFactory();
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final boolean isIdentifiable = IIdentifiable.class.isAssignableFrom(typeToken.getRawType());
final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
if ( isIdentifiable ) {
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
final IIdentifiable<?> identifiable = (IIdentifiable<?>) value;
final Object id = identifiable.getId();
if ( !traversedEntityIds.contains(id) ) {
delegateAdapter.write(out, value);
traversedEntityIds.add(id);
} else {
out.beginObject();
out.name(REF_ID_PROPERTY_NAME);
writeSimpleValue(out, id);
out.endObject();
}
}
#Override
public T read(final JsonReader in) {
throw new UnsupportedOperationException();
}
};
}
return delegateAdapter;
}
}
The type adapter firstly tries to check if a given entity has been already traversed. If yes, then it's writing a special object similar to your one (the behavior could be rewritten via the strategy pattern, of course, but let it be more simple). If no, then the default type adapter is obtained, and then the given entity is delegated to that adapter, and registered as a traversed one if the latter type adapter succeeds.
The rest
And here is the rest.
SystemNames.java
final class SystemNames {
private SystemNames() {
}
private static final String SYSTEM_PREFIX = "__$";
static final String ID_PROPERTY_NAME = SYSTEM_PREFIX + "id";
static final String REF_ID_PROPERTY_NAME = SYSTEM_PREFIX + "refId";
}
GsonJsonWriters.java
final class GsonJsonWriters {
private GsonJsonWriters() {
}
static void writeSimpleValue(final JsonWriter writer, final Object value)
throws IOException {
if ( value == null ) {
writer.nullValue();
} else if ( value instanceof Double ) {
writer.value((double) value);
} else if ( value instanceof Long ) {
writer.value((long) value);
} else if ( value instanceof String ) {
writer.value((String) value);
} else if ( value instanceof Boolean ) {
writer.value((Boolean) value);
} else if ( value instanceof Number ) {
writer.value((Number) value);
} else {
throw new IllegalArgumentException("Cannot handle values of type " + value);
}
}
}
Testing
In the test below, there are three entities identified by FOO, BAR, and BAZ string identifiers. All of them have circular dependencies like this:
FOO -> BAR, BAR -> BAZ, BAZ -> FOO using the next property;
FOO -> [BAR, BAZ], BAR -> [FOO, BAZ], BAZ -> [FOO, BAR] using the entities property.
Since the type adapter factory is stateful, even GsonBuilder must be created from scratch thus not having "spoiled" state between use. Simply speaking, once a Gson instance is used once, it must be disposed, so there are GsonBuilder suppliers in the test below.
public final class Q41213747Test {
private static final Entity foo = entity("FOO");
private static final Entity bar = entity("BAR");
private static final Entity baz = entity("BAZ");
static {
foo.setAll(bar, baz).setNext(bar);
bar.setAll(foo, baz).setNext(baz);
baz.setAll(foo, bar).setNext(foo);
}
#Test
public void testSerializeSameJson() {
final String json1 = newSerializingGson().toJson(foo);
final String json2 = newSerializingGson().toJson(foo);
assertThat("Must be the same between the calls because the GSON instances are stateful", json1, is(json2));
}
#Test
public void testSerializeNotSameJson() {
final Gson gson = newSerializingGson();
final String json1 = gson.toJson(foo);
final String json2 = gson.toJson(foo);
assertThat("Must not be the same between the calls because the GSON instance is stateful", json1, is(not(json2)));
}
#Test
public void testOutput() {
out.println(newSerializingGson().toJson(foo));
}
private static Gson newSerializingGson() {
return newSerializingGson(GsonBuilder::new);
}
private static Gson newSerializingGson(final Supplier<GsonBuilder> defaultGsonBuilderSupplier) {
return defaultGsonBuilderSupplier.get()
.registerTypeAdapterFactory(identitySerializingTypeAdapterFactory())
.create();
}
}
{
"__$id": "FOO",
"entities": [
{
"__$id": "BAR",
"entities": [
{
"__$refId": "FOO"
},
{
"__$id": "BAZ",
"entities": [
{
"__$refId": "FOO"
},
{
"__$refId": "BAR"
}
],
"next": {
"__$refId": "FOO"
}
}
],
"next": {
"__$refId": "BAZ"
}
},
{
"__$refId": "BAZ"
}
],
"next": {
"__$refId": "BAR"
}
}
Deserialization of such stuff looks really complicated. At least using GSON facilities.
Do you consider rethinking your JSON model in order to avoid circular dependencies in JSON output? Maybe decomposing your objects to a single map like Map<ID, Object> and making references transient or #Expose-annotated could be easier for you to use? It would simplify deserialization as well.

Setting Default value to a variable when deserializing using gson

I am trying to convert JSON to Java object. When a certain value of a pair is null, it should be set with some default value.
Here is my POJO:
public class Student {
String rollNo;
String name;
String contact;
String school;
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
Example JSON object:
{
"rollNo":"123", "name":"Tony", "school":null
}
So if school is null, I should make this into a default value, such as "school":"XXX". How can I configure this with Gson while deserializing the objects?
If the null is in the JSON, Gson is going to override any defaults you might set in the POJO. You could go to the trouble of creating a custom deserializer, but that might be overkill in this case.
I think the easiest (and, arguably best given your use case) thing to do is the equivalent of Lazy Loading. For example:
private static final String DEFAULT_SCHOOL = "ABC Elementary";
public String getSchool() {
if (school == null) school == DEFAULT_SCHOOL;
return school;
}
public void setSchool(String school) {
if (school == null) this.school = DEFAULT_SCHOOL;
else this.school = school;
}
Note: The big problem with this solution is that in order to change the Defaults, you have to change the code. If you want the default value to be customizable, you should go with the custom deserializer as linked above.
I think that the way to do this is to either write your no-args constructor to fill in default values, or use a custom instance creator. The deserializer should then replace the default values for all attributes in the JSON object being deserialized.
I was having the same issue, until I found this great solution.
For reference, you can create a post-processing class:
interface PostProcessable {
fun gsonPostProcess()
}
class PostProcessingEnabler : TypeAdapterFactory {
fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> {
val delegate = gson.getDelegateAdapter(this, type)
return object : TypeAdapter<T>() {
#Throws(IOException::class)
fun write(out: JsonWriter, value: T) {
delegate.write(out, value)
}
#Throws(IOException::class)
fun read(`in`: JsonReader): T {
val obj = delegate.read(`in`)
if (obj is PostProcessable) {
(obj as PostProcessable).gsonPostProcess()
}
return obj
}
}
}
}
Register it like this:
GsonBuilder().registerTypeAdapterFactory(PostProcessingEnabler())
Implement it on your model:
class MyClass : Serializable, PostProcessable {
// All your variable data
override fun gsonPostProcess() {
// All your post processing logic you like on your object
// set default value for example
}
}
And finally use it when converting json string:
var myObject = myGson.fromJson(myObjectJson, MyClass::class)
Or using retrofit2:
val api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder().registerTypeAdapterFactory(
GsonPostProcessingEnabler()
).create()
)
)
.client(OkHttpClient.Builder().build())
.build()
.create(AccountApi::class.java)
You can simply make a universal function that checks for null
model.SchoolName= stringNullChecker(model.SchoolName);
public static String stringNullChecker(String val) {
if (null == val) val = "";
return val;
}

class A declares multiple JSON fields

i have a class A which has some private fields and the same class extends another class B which also has some private fields which are in class A.
public class A extends B {
private BigDecimal netAmountTcy;
private BigDecimal netAmountPcy;
private BigDecimal priceTo;
private String segment;
private BigDecimal taxAmountTcy;
private BigDecimal taxAmountPcy;
private BigDecimal tradeFeesTcy;
private BigDecimal tradeFeesPcy;
// getter and setter for the above fields
}
and class B has got some private fiedls which are in class A
now when i try to create JSON string from above class A i get the following exception :
class com.hexgen.ro.request.A declares multiple JSON fields named netAmountPcy
How to fix this?
Since they are private fields there should not be any problem while creating json string i guess but i am not sure.
i create json string like the following :
Gson gson = new Gson();
tempJSON = gson.toJson(obj);
here obj is the object of class A
Since they are private fields there should not be any problem while creating json string
I don't think this statement is true, GSON looks up at the object's private fields when serializing, meaning all private fields of superclass are included, and when you have fields with same name it throws an error.
If there's any particular field you don't want to include you have to mark it with transient keyword, eg:
private transient BigDecimal tradeFeesPcy;
This is a bit late, but I ran into this exact same problem as well. The only thing was that I wasn't able to modify the superclass as that code wasn't mine. The way that I resolved this was by creating an exclusion strategy that skipped any field that had a field of the same name present in a superclass. Here is my code for that class:
public class SuperclassExclusionStrategy implements ExclusionStrategy
{
public boolean shouldSkipClass(Class<?> arg0)
{
return false;
}
public boolean shouldSkipField(FieldAttributes fieldAttributes)
{
String fieldName = fieldAttributes.getName();
Class<?> theClass = fieldAttributes.getDeclaringClass();
return isFieldInSuperclass(theClass, fieldName);
}
private boolean isFieldInSuperclass(Class<?> subclass, String fieldName)
{
Class<?> superclass = subclass.getSuperclass();
Field field;
while(superclass != null)
{
field = getField(superclass, fieldName);
if(field != null)
return true;
superclass = superclass.getSuperclass();
}
return false;
}
private Field getField(Class<?> theClass, String fieldName)
{
try
{
return theClass.getDeclaredField(fieldName);
}
catch(Exception e)
{
return null;
}
}
}
I then set the Serialization and Deserialization exclusion strategies in the builder as follows:
builder.addDeserializationExclusionStrategy(new SuperclassExclusionStrategy());
builder.addSerializationExclusionStrategy(new SuperclassExclusionStrategy());
Hopefully this helps someone!
The same error message also happens if you have different fields, but they have the same #SerializedName.
#SerializedName("date_created")
private Date DateCreated;
#SerializedName("date_created")
private Integer matchTime;
Doing copy/paste you can simply make such mistake. So, look into the the class and its ancestors and check for that.
You cannot have two fields with the same name.
You cannot have two fields with the same serialized name.
Types are irrelevant for these rules.
I used GsonBuilder and ExclusionStrategy to avoid the redundant fields as below, it is simple and straight forward.
Gson json = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
if(f.getName().equals("netAmountPcy")){
return true;
}
return false;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
Add following lines at the bottom of proguard.config
(if you are using proguard in project)
-keepclassmembers class * {
private <fields>;
}
I don't think you should make the members transient, this might lead to errors because members that you might need in the future might be hidden.
How I solved this problem is to use a custom naming strategy and append the full class name to the Json, the downside of this is that it would lead to larger Json and if you need it for something like a Rest Api it would be weird for clients to name the fields that way, but I only needed to serialize to write to disk on android.
So here is an implementation of a custom naming strategy in Kotlin
import com.google.gson.FieldNamingStrategy
import java.lang.reflect.Field
class GsonFieldNamingStrategy : FieldNamingStrategy {
override fun translateName(field: Field?): String? {
return "${field?.declaringClass?.canonicalName}.${field?.name}"
}
}
So for all fields, the full canonical name would be appended, this would make the child class have a different name from the parent class, but when deserializing, the child class value would be used.
In kotlin adding the #Transient annotation for the variable on the parent class did the trick for me on a sealed class with open variables.
Solution for Kotlin, as suggested #Adrian-Lee, you have to tweak some Null Checks
class SuperclassExclusionStrategy : ExclusionStrategy {
override fun shouldSkipClass(clazz: Class<*>?): Boolean {
return false
}
override fun shouldSkipField(f: FieldAttributes?): Boolean {
val fieldName = f?.name
val theClass = f?.declaringClass
return isFieldInSuperclass(theClass, fieldName)
}
private fun isFieldInSuperclass(subclass: Class<*>?, fieldName: String?): Boolean {
var superclass: Class<*>? = subclass?.superclass
var field: Field?
while (superclass != null) {
field = getField(superclass, fieldName)
if (field != null)
return true
superclass = superclass.superclass
}
return false
}
private fun getField(theClass: Class<*>, fieldName: String?): Field? {
return try {
theClass.getDeclaredField(fieldName)
} catch (e: Exception) {
null
}
}
}
In my case I was dumb enough to register an adapter with X class, and try to serialize fromJson with Y class:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Game.class, new TournamentSerializer());
final Gson gson = gsonBuilder.create();
createdTournament = gson.fromJson(jsonResponse.toString(), Tournament.class);
For Kotlin-er:
val fieldsToExclude = listOf("fieldToExclude", "otherFieldToExclude")
GsonBuilder()
.setExclusionStrategies(object : ExclusionStrategy {
override fun shouldSkipField(f: FieldAttributes?) = f?.let { fieldsToExclude.contains(it.name) } ?: false
override fun shouldSkipClass(clazz: Class<*>?) = false
})
.create()

Categories

Resources