I have requirement where I need to convert java object to json.
I am using Gson for that but i need the converter to only serialize the non null or not empty values.
For example:
//my java object looks like
class TestObject{
String test1;
String test2;
OtherObject otherObject = new OtherObject();
}
now my Gson instance to convert this object to json looks like
Gson gson = new Gson();
TestObject obj = new TestObject();
obj.test1 = "test1";
obj.test2 = "";
String jsonStr = gson.toJson(obj);
println jsonStr;
In the above print, the result is
{"test1":"test1", "test2":"", "otherObject":{}}
Here i just wanted the result to be
{"test1":"test1"}
Since the test2 is empty and otherObject is empty, i don't want them to be serialized to json data.
Btw, I am using Groovy/Grails so if there is any plugin for this that would be good, if not any suggestion to customize the gson serialization class would be good.
Create your own TypeAdapter
public class MyTypeAdapter extends TypeAdapter<TestObject>() {
#Override
public void write(JsonWriter out, TestObject value) throws IOException {
out.beginObject();
if (!Strings.isNullOrEmpty(value.test1)) {
out.name("test1");
out.value(value.test1);
}
if (!Strings.isNullOrEmpty(value.test2)) {
out.name("test2");
out.value(value.test1);
}
/* similar check for otherObject */
out.endObject();
}
#Override
public TestObject read(JsonReader in) throws IOException {
// do something similar, but the other way around
}
}
You can then register it with Gson.
Gson gson = new GsonBuilder().registerTypeAdapter(TestObject.class, new MyTypeAdapter()).create();
TestObject obj = new TestObject();
obj.test1 = "test1";
obj.test2 = "";
System.out.println(gson.toJson(obj));
produces
{"test1":"test1"}
The GsonBuilder class has a bunch of methods to create your own serialization/deserialization strategies, register type adapters, and set other parameters.
Strings is a Guava class. You can do your own check if you don't want that dependency.
What I personally don't like in TypeAdapter using answer is the fact you need to describe every field of your entire class which could have lets say 50 fields (which means 50 if blocks in TypeAdapter).
My solution is based on Reflection and a fact Gson will not serialize null values fields by default.
I have a special class which holds data for API to create document called DocumentModel, which has about 50 fields and I don't like to send String fields with "" (empty but not null) values or empty arrays to server. So I created a special method which returns me a copy of my object with all empty fields nulled. Note - by default all arrays in my DocumentModel instance are initialized as empty (zero length) arrays and thus they are never null, you should probably check your arrays for null before checking their length.
public DocumentModel getSerializableCopy() {
Field fields[] = new Field[]{};
try {
// returns the array of Field objects representing the public fields
fields = DocumentModel.class.getDeclaredFields();
} catch (Exception e) {
e.printStackTrace();
}
DocumentModel copy = new DocumentModel();
Object value;
for (Field field : fields) {
try {
value = field.get(this);
if (value instanceof String && TextUtils.isEmpty((String) value)) {
field.set(copy, null);
// note: here array is not being checked for null!
else if (value instanceof Object[] && ((Object[]) value).length == 0) {
field.set(copy, null);
} else
field.set(copy, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return copy;
}
Using this method I don't care if some fields was added or removed after this method was written or whatever. The only problem left - is checking custom type fields, which are not String or array, but this depends to particular class and should be extra coded in if/else blocks.
It seems to me the problem is not with gson. Gson correctly keeps track of the difference between null and an empty string. Are you sure you want to erase that distinction? Are you sure all classes that use TestObject don't care?
What you could do if you don't care about the difference is to change the empty strings to null within a TestObject before serializing it. Or better, make the setters in TestObject such that an empty string is set to null; that way you define rigidly within the class that an empty string is the same as null. You'll have to make sure the values cannot be set outside the setters.
I have ran into the same problem and found 2 distinct solutions
Write a custom TypeAdapter for each field class
TypeAdapter example for String class:
#SuppressWarnings("rawtypes")
public class JSONStringAdapter extends TypeAdapter {
#Override
public String read(JsonReader jsonReader) throws IOException {
String value = jsonReader.nextString();
if(value == null || value.trim().length() == 0) {
return null;
} else {
return value;
}
}
#Override
public void write(JsonWriter jsonWriter, Object object) throws IOException {
String value = String.valueOf(object);
if(value == null || value.trim().length() == 0) {
jsonWriter.nullValue();
} else {
jsonWriter.value(value);
}
}
}
Use:
public class Doggo {
#JsonAdapter(JSONStringAdapter.class)
private String name;
public Doggo(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Doggo aDoggo = new Doggo("");
String jsonString = new Gson().toJson(aDoggo);
}
}
Process the object manually before generating the JSON string
Seems to work on anything, haven't tested the performance:
public static boolean removeEmpty(JSONObject source) {
if (null == source || source.length() == 0) {
return true;
}
boolean isJsonObjectEmpty = false;
for (String key : JSONObject.getNames(source)) {
Object value = source.get(key);
boolean isValueEmpty = isValueEmpty(value);
if(isValueEmpty) {
source.remove(key);
}
}
if(source.length() == 0) {
isJsonObjectEmpty = true;
}
return isJsonObjectEmpty;
}
private static boolean isValueEmpty(Object value) {
if (null == value) {
return true;
}
if (value instanceof JSONArray) {
JSONArray arr = (JSONArray) value;
if(arr.length() > 0) {
List<Integer> indextesToRemove = new ArrayList<>();
for(int i = 0; i< arr.length(); i++) {
boolean isValueEmpty = isValueEmpty(arr.get(i));
if(isValueEmpty) {
indextesToRemove.add(i);
};
}
for(Integer index : indextesToRemove) {
arr.remove(index);
}
if(arr.length() == 0) {
return true;
}
} else {
return true;
}
} else if (value instanceof JSONObject) {
return removeEmpty((JSONObject) value);
} else {
if (JSONObject.NULL.equals(value)
|| null == value
|| value.toString().trim().length() == 0)
) {
return true;
}
}
return false;
}
Use:
public class Doggo {
private String name;
public Doggo(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Doggo aDoggo = new Doggo("");
// if you are not using Type Adapters for your fields
JSONObject aJSONObject1 = new JSONObject(aDoggo);
removeEmpty(aJSONObject1);
String jsonString1 = aJSONObject1.toString();
// if you are using Type Adapters for your fields
Gson gsonParser = new Gson();
JSONObject aJSONObject2 = new JSONObject(gsonParser .toJson(aDoggo));
removeEmpty(aJSONObject2);
String jsonString2 = aJSONObject2.toString();
}
}
I am creating java app which will allow storing objects in database. What I want to do is generic implementation so it could load json and create java class from it. This is what a code should look like:
SomeClass someObject= data.getValue(SomeClass.class);
Lets say that data would be a json object. How should I implement getValue() method so it will allow me to create class from it. I don't want SomeClass to extend anything other then Object. I think that this should be done using generic classes but so far I have not worked with generic classes like this. Can you please point to a best way on how to acomplish this? Example code would be best.
Many thanks
You can consult the source code of Jackson library and look inside (or debug) the method BeanDeserializer#vanillaDeserialize(), there you'll find the loop which traverse through all json tokens, finds the corresponding fields and sets their values.
As a proof of concept, I've extracted part of the logic from Jacskson and wrapped it inside a naive (and fragile) object mapper and a naive (and fragile) json parser:
public static class NaiveObjectMapper {
private Map<String, Object> fieldsAndMethods;
private NaiveJsonParser parser;
public <T> T readValue(String content, Class<T> valueType) {
parser = new NaiveJsonParser(content);
try {
// aggregate all value type fields and methods inside a map
fieldsAndMethods = new HashMap<>();
for (Field field : valueType.getDeclaredFields()) {
fieldsAndMethods.put(field.getName(), field);
}
for (Method method : valueType.getMethods()) {
fieldsAndMethods.put(method.getName(), method);
}
// create an instance of value type by calling its default constructor
Constructor<T> constructor = valueType.getConstructor();
Object bean = constructor.newInstance(new Object[0]);
// loop through all json nodes
String propName;
while ((propName = parser.nextFieldName()) != null) {
// find the corresponding field
Field prop = (Field) fieldsAndMethods.get(propName);
// get and set field value
deserializeAndSet(prop, bean);
}
return (T) bean;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private void deserializeAndSet(Field prop, Object bean) {
Class<?> propType = prop.getType();
Method setter = (Method) fieldsAndMethods.get(getFieldSetterName(prop));
try {
if (propType.isPrimitive()) {
if (propType.getName().equals("int")) {
setter.invoke(bean, parser.getIntValue());
}
} else if (propType == String.class) {
setter.invoke(bean, parser.getTextValue());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private String getFieldSetterName(Field prop) {
String propName = prop.getName();
return "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
}
}
class NaiveJsonParser {
String[] nodes;
int currentNodeIdx = -1;
String currentProperty;
String currentValueStr;
public NaiveJsonParser(String content) {
// split the content into 'property:value' nodes
nodes = content.replaceAll("[{}]", "").split(",");
}
public String nextFieldName() {
if ((++currentNodeIdx) >= nodes.length) {
return null;
}
String[] propertyAndValue = nodes[currentNodeIdx].split(":");
currentProperty = propertyAndValue[0].replace("\"", "").trim();
currentValueStr = propertyAndValue[1].replace("\"", "").trim();
return currentProperty;
}
public String getTextValue() {
return String.valueOf(currentValueStr);
}
public int getIntValue() {
return Integer.valueOf(currentValueStr).intValue();
}
}
public static class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "id = " + id + ", name = \"" + name + "\"";
}
}
To see the deserialization in action run:
String json = "{\"id\":1, \"name\":\"jsmith\"}";
NaiveObjectMapper objectMapper = new NaiveObjectMapper();
User user = objectMapper.readValue(json, User.class);
System.out.println(user);
Or try online.
However I recommend not to reinvent the wheel and use Jackson and in case you need some custom actions you can use custom deserialization, see here and here.
My use case was to write a generic CSV transformer, which should be able to convert any Java POJO to CSV string.
My Implementation :
public <T> List<String> convertToString(List<T> objectList) {
List<String> stringList = new ArrayList<>();
char delimiter = ',';
char quote = '"';
String lineSep = "\n";
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(!HOW_TO!);
for (T object : objectList) {
try {
String csv = mapper.writer(schema
.withColumnSeparator(delimiter)
.withQuoteChar(quote)
.withLineSeparator(lineSep)).writeValueAsString(object);
} catch (JsonProcessingException e) {
System.out.println(e);
}
}
return stringList;
}
I was using Jackson-dataformat-csv library, but I'm stuck with !HOW_TO! part, ie How to extract the .class of the object from the objectList. I was studying and came across Type Erasure, So I think it is somehow not possible other than giving the .class as parameter to my function. But I'm also extracting this object list from generic entity using Java Reflection, so I can't have the option to provide the .class params.
Is there a workaround for this?
OR
Any other approaches/libraries where I can convert a generic List<T> objectList to List<String> csvList with functionality of adding delimiters, quote characters, line separators etc.
Thanks!
I have created a CSVUtil Class similar to below which uses java reflection.
Example to use below CSVUtil
Assuming POJO Student ,
List<Student> StudentList = new ArrayList<Student>();
String StudentCSV = CSVUtil.toCSV(StudentList,' ',false);
import java.lang.reflect.Field;
import java.util.List;
import java.util.logging.Logger;
CSVUtil class
public class CSVUtil {
private static final Logger LOGGER = Logger.getLogger(CSVUtil.class .getName());
private final static char DEFAULT_SEPARATOR = ' ';
public static String toCSV(List<?> objectList, char separator, boolean displayHeader) {
StringBuilder result =new StringBuilder();
if (objectList.size() == 0) {
return result.toString();
}
if(displayHeader){
result.append(getHeaders(objectList.get(0),separator));
result.append("\n");
}
for (Object obj : objectList) {
result.append(addObjectRow(obj, separator)).append("\n");
}
return result.toString();
}
public static String getHeaders(Object obj,char separator) {
StringBuilder resultHeader = new StringBuilder();
boolean firstField = true;
Field fields[] = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String value;
try {
value = field.getName();
if(firstField){
resultHeader.append(value);
firstField = false;
}
else{
resultHeader.append(separator).append(value);
}
field.setAccessible(false);
} catch (IllegalArgumentException e) {
LOGGER.severe(e.toString());
}
}
return resultHeader.toString();
}
public static String addObjectRow(Object obj, char separator) {
StringBuilder csvRow =new StringBuilder();
Field fields[] = obj.getClass().getDeclaredFields();
boolean firstField = true;
for (Field field : fields) {
field.setAccessible(true);
Object value;
try {
value = field.get(obj);
if(value == null)
value = "";
if(firstField){
csvRow.append(value);
firstField = false;
}
else{
csvRow.append(separator).append(value);
}
field.setAccessible(false);
} catch (IllegalArgumentException | IllegalAccessException e) {
LOGGER.severe(e.toString());
}
}
return csvRow.toString();
}
}
There is a simple option. I've added some lines to your code to show it :
public <T> List<String> convertToString(List<T> objectList) {
if(objectList.isEmpty())
return Collections.emptyList();
T entry = objectList.get(0);
List<String> stringList = new ArrayList<>();
char delimiter = ',';
char quote = '"';
String lineSep = "\n";
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(entry.getClass());
for (T object : objectList) {
try {
String csv = mapper.writer(schema
.withColumnSeparator(delimiter)
.withQuoteChar(quote)
.withLineSeparator(lineSep)).writeValueAsString(object);
stringList.add(csv);
} catch (JsonProcessingException e) {
System.out.println(e);
}
}
return stringList;
}
The trick is to get one of the elements of the list. In order to avoid crashs I've added a little data integrity test at the beginning that return an unmodifiable empty list in the case there are no items in the input list.
Then you retrieve an instance of your Object and use that to get the class.
Alternatively if the convertToString method is in a parametrized class you can do that in a slightly different way
public class GenericClass<T> {
private final Class<T> type;
public GenericClass(Class<T> type) {
this.type = type;
}
public Class<T> getMyType() {
return this.type;
}
}
This solution allow you to get the class of T. I don't think you'll need it for this question but it might comes in handy.
It seems this problem is just harder than most people would like it to be as a result of how Java does generics. Bruno's answer shows options that might work if you can make certain assumptions or can structure your code a certain way.
Another option that should work for your case can be found by way of the answers to this other question: How to get a class instance of generics type T
In there you'll find a link to an article: http://blog.xebia.com/acessing-generic-types-at-runtime-in-java/
This describes how to use the ParameterizedType of an object's superclass. You can apply that to your List object and hopefully it will work for you. This only may luckily work in this case, because you're taking as a parameter an object with a superclass whose type parameters match what you need.
Truly in general, we can't rely on knowing the type parameters at runtime. We can at best maybe use type tokens (parameter of type Class<T>)
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.
TLDR: I'd like to know how to extend fit.TypeAdaptor so that I can invoke a method that expects parameters as default implementation of TypeAdaptor invokes the binded (bound ?) method by reflection and assumes it's a no-param method...
Longer version -
I'm using fit to build a test harness for my system (a service that returns a sorted list of custom objects). In order to verify the system, I thought I'd use fit.RowFixture to assert attributes of the list items.
Since RowFixture expects the data to be either a public attribute or a public method, I thought of using a wrapper over my custom object (say InstanceWrapper) - I also tried to implement the suggestion given in this previous thread about formatting data in RowFixture.
The trouble is that my custom object has around 41 attributes and I'd like to provide testers with the option of choosing which attributes they want to verify in this RowFixture. Plus, unless I dynamically add fields/methods to my InstanceWrapper class, how will RowFixture invoke either of my getters since both expect the attribute name to be passed as a param (code copied below) ?
I extended RowFixture to bind on my method but I'm not sure how to extend TypeAdaptor so that it invokes with the attr name..
Any suggestions ?
public class InstanceWrapper {
private Instance instance;
private Map<String, Object> attrs;
public int index;
public InstanceWrapper() {
super();
}
public InstanceWrapper(Instance instance) {
this.instance = instance;
init(); // initialise map
}
private void init() {
attrs = new HashMap<String, Object>();
String attrName;
for (AttrDef attrDef : instance.getModelDef().getAttrDefs()) {
attrName = attrDef.getAttrName();
attrs.put(attrName, instance.getChildScalar(attrName));
}
}
public String getAttribute(String attr) {
return attrs.get(attr).toString();
}
public String description(String attribute) {
return instance.getChildScalar(attribute).toString();
}
}
public class MyDisplayRules extends fit.RowFixture {
#Override
public Object[] query() {
List<Instance> list = PHEFixture.hierarchyList;
return convertInstances(list);
}
private Object[] convertInstances(List<Instance> instances) {
Object[] objects = new Object[instances.size()];
InstanceWrapper wrapper;
int index = 0;
for (Instance instance : instances) {
wrapper = new InstanceWrapper(instance);
wrapper.index = index;
objects[index++] = wrapper;
}
return objects;
}
#Override
public Class getTargetClass() {
return InstanceWrapper.class;
}
#Override
public Object parse(String s, Class type) throws Exception {
return super.parse(s, type);
}
#Override
protected void bind(Parse heads) {
columnBindings = new TypeAdapter[heads.size()];
for (int i = 0; heads != null; i++, heads = heads.more) {
String name = heads.text();
String suffix = "()";
try {
if (name.equals("")) {
columnBindings[i] = null;
} else if (name.endsWith(suffix)) {
columnBindings[i] = bindMethod("description", name.substring(0, name.length()
- suffix.length()));
} else {
columnBindings[i] = bindField(name);
}
} catch (Exception e) {
exception(heads, e);
}
}
}
protected TypeAdapter bindMethod(String name, String attribute) throws Exception {
Class partypes[] = new Class[1];
partypes[0] = String.class;
return PHETypeAdaptor.on(this, getTargetClass().getMethod("getAttribute", partypes), attribute);
}
}
For what it's worth, here's how I eventually worked around the problem:
I created a custom TypeAdapter (extending TypeAdapter) with the additional public attribute (String) attrName. Also:
#Override
public Object invoke() throws IllegalAccessException, InvocationTargetException {
if ("getAttribute".equals(method.getName())) {
Object params[] = { attrName };
return method.invoke(target, params);
} else {
return super.invoke();
}
}
Then I extended fit.RowFixture and made the following overrides:
public getTargetClass() - to return my class reference
protected TypeAdapter bindField(String name) throws Exception - this is a protected method in ColumnFixture which I modified so that it would use my class's getter method:
#Override
protected TypeAdapter bindField(String name) throws Exception {
String fieldName = camel(name);
// for all attributes, use method getAttribute(String)
Class methodParams[] = new Class[1];
methodParams[0] = String.class;
TypeAdapter a = TypeAdapter.on(this, getTargetClass().getMethod("getAttribute", methodParams));
PHETypeAdapter pheAdapter = new PHETypeAdapter(fieldName);
pheAdapter.target = a.target;
pheAdapter.fixture = a.fixture;
pheAdapter.field = a.field;
pheAdapter.method = a.method;
pheAdapter.type = a.type;
return pheAdapter;
}
I know this is not a neat solution, but it was the best I could come up with. Maybe I'll get some better solutions here :-)