Custom Xstream/JSON converter for enum - java

I have the following Enum:
public enum MyState {
Open("opened"),
Close("closed"),
Indeterminate("unknown");
private String desc;
private MyState(String desc) {
setDesc(desc);
}
public String getDesc() {
return this.desc;
}
private void setDesc(String desc) {
this.desc = desc;
}
}
I am trying to write an XStream Converter that will know to map back a JSON element "mystate" to a MyState instance.
"someJson": {
"object1": {
"mystate": closed
}
}
This should produce, amongst other objects (someJson and object1) a MyState.Close instance. I've started the Converter, but haven't gotten very far:
public class MyStateEnumConverter implement Converter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyState.class);
}
#Override
public void marshal(Object value, HierarchialStreamWriter writer, MarshallingContext context) {
??? - no clue here
}
#Override
public Object unmarshal(HierarchialStreamReader reader, UnmarshallingContext context) {
??? - no clue here
}
}
Then, to create the mapper and use it:
XStream mapper = new XStream(new JettisonMappedXmlDriver());
mapper.registerConverter(new MyStateEnumConverter);
SomeJson jsonObj = mapper.fromXML(jsonString);
// Should print "closed"
System.out.println(jsonObject.getObject1().getMyState().getDesc());
How can I implement marshal and unmarshal so thatI get the desired mapping? Thanks in advance!

You can accomplish this by doing 2 things:
Adding a lookup method as well as a toString() override to your enum (MyStateEnum); and
Extending XStream's AbstractSingleValueConverter instead of implementing Converter
MyStateEnum:
public enum MyStateEnum {
// Everything you had is fine
// But now, add:
public static MyStateEnum getMyStateByDesc(String desc) {
for(MyStateEnum myState : MyStateEnum.values())
if(myState.getDesc().equals(desc))
return myState;
return null;
}
#Override
public String toString() {
return getDesc();
}
}
MyStateEnumConverter:
public class MyStateEnumConverter extends AbstractSingleValueConverter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyStateEnum.class);
}
#Override
public Object fromString(String parsedText) {
return MyStateEnum.getMyStateByDesc(parsedText);
}
}
By adding getMyStateByDesc(String) to your enum, you now have a way to look up all the various enumerated values from the outside, by providing a desc string. The MyStateEnumConverter (which extends AbstractSingleValueConverter) uses your toString() override under the hood to associate aMyStateEnum instance with a text string.
So when XStream is parsing the JSON, it sees a JSON object of, say, "opened", and this new converter knows to pass "opened" into the converter's fromString(String) method, which in turn uses getMyStateByDesc(String) to lookup the appropriate enum instance.
Don't forget to register your converter with your XStream instance as you already showed in your original question.

You can use the EnumToStringConverter
Documentation
Example
#XStreamConverter(EnumToStringConverter.class)
public enum MyStateEnum {
enter code here
...
Use xstream.autodetectAnnotations(true)

Why are you using xstream for json support? You have a couple of other libraries specialized in json and that do it well. Also closed without quotes is not valid json.
Try for example Genson, it will work out of the box.
The values in the json stream would be "Close", "Indeterminate", etc and when deserializing it will produce the correct enum.
class SomeObject {
private MyState state;
...
}
Genson genson = new Genson();
// json = {"state" : "Indeterminate"}
String json = genson.serialize(new SomeObject(MyState.Indeterminate));
// deserialize back
SomeObject someObject = genson.deserialize(json, SomeObject.class);
// will print unknown
System.out.println(someObject.getDesc());

Related

Generic Class for a GSON LinkedHashMap

I have been working on this solution for months and I have come to the conclusion that there is no clean way to achieve what I am trying to achieve. I feel as though my education in polymorphism is failing me, so I've come to StackOverflow to get a second opinion. Sorry if this seems long and convoluted. That's been my brain for the past couple of months and at this point I'm out of ideas. I'm hoping somebody can take a look and see that I could've avoided all this mess by doing it some other way.
What I am trying to achieve is two generic classes: One that can represent any "saveable" object, and one that can represent a list of saveable objects (or what I call a "store"). A saveable object can save itself using GSON, and a store can also save itself using GSON to a JSON file. The difference being that saveable objects are generically representing any GSON object that can be saved, whereas stores are extending from saveables to become a saveable hash map of objects via IDs.
An example output I am looking for is as so:
Imagine I have an object with a uuid string field and a name string field. I want to be able to create a Store, which is a LinkedHashMap, of these objects, but also extend a Saveable to allow the objects to be saved as so:
test.json
{"dbf39199209e466ebed0061a3491ed9e":{"uuid":"dbf39199209e466ebed0061a3491ed9e","name":"Example Name"}}
I would also like to be able to load this JSON back into the objects via the Store's load method.
An example code usage would be like so:
Store<User> users = new Store<>();
users.load();
users.add(new User("dbf39199209e466ebed0061a3491ed9e", "Example Name"));
users.save();
My Attempts
Saveables
What I expect a "saveable" object to be able to do is as follows: provide a non-argumented method for saving and provide a non-argumented method for loading. A saveable object represents any object that can be saved via GSON. It contains two fields: a Gson gson object and a Path location. I provide those in the constructor of my saveable. I then want to provide two methods: a Saveable#save() method and a Saveable#load() method (or a static Saveable#load() method, I am indifferent). The way you use a Saveable object is by extending it (so it is abstract) to another object representing something, say, TestSaveable, and then the usage is as so:
TestSaveable saveable = new TestSaveable(8);
saveable.save(); // Saves data
saveable.setData(4);
saveable = saveable.load(); // Loads old data
I also would like a saveable object to be able to handle a generic, such as an integer (think of the last example but with an integer generic). This would allow me to execute my next plan for Stores.
My attempt at an implementation was the following:
public abstract class Saveable {
private transient Gson gson;
private transient Path location;
public Saveable(Gson gson, Path location) {
this.gson = gson;
this.location = location;
}
#SuppressWarnings("unchecked")
public <T extends Saveable> T save() throws IOException {
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(location, gson.toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
protected <T extends Saveable> T load(Class<T> clazz, #NotNull Class<?>... generics) throws IOException {
if (!Files.exists(location)) {
return this.save();
} else {
InstanceCreator<Saveable> creator = type -> this;
Type type = TypeToken.getParameterized(clazz, generics).getType();
Gson newGson = gson.newBuilder().registerTypeAdapter(type, creator).create();
return newGson.fromJson(Files.newBufferedReader(location), type);
}
}
}
Unfortunately, this attempt failed in my goal, because upon making my TestSaveable class users still had to pass the generic through for loading:
public class TestSaveable<T> extends Saveable {
public boolean testBool = false;
public T value;
public TestSaveable(T value) {
super(new Gson(), Path.of("test.json"));
this.value = value;
}
public final TestSaveable<T> load(Class<T> generic) throws IOException {
return super.load(TestSaveable.class, generic);
}
}
However, through this I did get a fairly clean implementation with the exception of little to no type checking at all and constantly having to add supressions for it:
public class Test {
public static void main(String[] args) {
try {
TestSaveable<Integer> storeB4 = new TestSaveable<>(5).save();
storeB4.value = 10;
TestSaveable<Integer> store = storeB4.load(Integer.class);
System.out.println("STORE: " + store);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Stores
Stores are an extension of saveables. A store is a LinkedHashMap which will quickly and easily save all of the objects in it as a map in GSON. Unfortunately, I'm not even sure where to start on this. I cannot extend two objects (the two being a LinkedHashMap<String, T> and a Saveable), but I also cannot use interfaces for the Saveable object.
I previously tried the following using the IStorable and ISaveable classes as an alternative to the abstract Saveable class I've shown you above, but this resulted in another very ugly and non-robust solution to my issue.
Saveable.java
public class Saveable {
// Suppress default constructor
private Saveable() {}
// Save a class to the specified location using the specified gson
public static <T extends ISaveable> T save(T instance) throws IOException {
Files.createDirectories(instance.getLocation().getParent());
Files.write(instance.getLocation(), instance.getGson().toJson(instance).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return instance;
}
// Load a file from the specified location using the specified gson and cast it to the specified class using the specified generic
public static <T extends ISaveable> ISaveable load(Path location, Gson gson, Class<T> clazz, Class<?> genericClazz) throws IOException {
if (!Files.exists(location)) {
return null;
} else {
TypeToken<?> type = genericClazz == null ? TypeToken.get(clazz) : TypeToken.getParameterized(clazz, genericClazz);
ISaveable saveable = gson.fromJson(Files.newBufferedReader(location), type.getType());
saveable.setGson(gson);
saveable.setLocation(location);
return saveable;
}
}
}
ISaveable.java
public interface ISaveable {
// Gson
Gson getGson();
void setGson(Gson gson);
// Location
Path getLocation();
void setLocation(Path location);
}
IStorable.java
public interface IStoreable {
String getUuid();
}
Store.java
public class Store<T extends IStoreable> extends LinkedHashMap<String, T> implements ISaveable {
private transient Path location;
private transient Gson gson;
public Store(Path location, Gson gson) {
this.location = location;
this.gson = gson;
}
public Store() {
this.location = null;
this.gson = null;
}
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
public Store<T> save() throws IOException {
return Saveable.save(this);
}
#SuppressWarnings("unchecked")
public static <T extends IStoreable> Store<T> load(Path location, Gson gson, Class<T> genericClazz) throws IOException {
ISaveable saveable = Saveable.load(location, gson, Store.class, genericClazz);
if (saveable == null) {
return new Store<T>(location, gson).save();
} else {
return (Store<T>) saveable;
}
}
}
This solution achieved me almost the result I was looking for, but fell short quickly on the loading process as well as just not being a robust solution, excluding the hundreds of Java practices I'm sure to have ruined at this point:
Store<ExampleStoreable> store = Store.load(Paths.get("storetest.json"), new Gson(), ExampleStoreable.class);
store.put(new ExampleStoreable("Example Name"));
store.save();
And before I get any comments saying I shouldn't be posting this on StackOverflow: if not here, where else? Please help point me in the right direction, I'd love to not be left in the dark.
Thanks if anyone is able to help and if not I understand. This isn't the easiest question by any means.
I was extremely close to the correct solution, but my logic just wasn't lining up.
The fixed load method is as follows:
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
Instead of attempting to prevent type erasure, and instead of passing the class every time we call the method, we just... pass it in the constructor. It was really that simple. I don't care about sending the type through the constructor, as long as .load() and .save() do not result in hundreds of lines of repetitive code.
I can't believe I was this close to the solution the whole time. It's incredible to me how simple this was. Guess that's the life of programming, right?
Here is the full class, which I determined was better as an interface called ISaveable.java:
public interface ISaveable {
Type getType();
Gson getGson();
Path getLocation();
/**
* Saves this object.
*
* #param <T> The extended object to cast to.
* #return The object after having been saved.
* #throws IOException Thrown if there was an exception while trying to save.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T save() throws IOException {
Path location = getLocation().toAbsolutePath();
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(getLocation(), getGson().toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
/**
* Loads this object.
*
* #param <T> The extended object to cast to.
* #return The object after loading the new values.
* #throws IOException Thrown if there was an exception while trying to load.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
}
An example implementation:
public class ExampleSaveable implements ISaveable {
private boolean testBoolean = false;
private String myString;
public ExampleSaveable(String myString) {
this.myString = myString;
}
#Override
public Gson getGson() {
return new Gson();
}
#Override
public Type getType() {
return TypeToken.get(ExampleSaveable.class).getType();
}
#Override
public Path getLocation() {
return Path.of("test.json");
}
}
And an example usage is like so:
ExampleSaveable saveable = new ExampleSaveable("My Data!").load();
saveable.myString = "This is a replacement string!";
saveable.save();
On the first run, the output is "My Data!", on the second, the output is "This is a replacement string!"
The corresponding output JSON was:
{"testBoolean":false,"myString":"This is a replacement string!"}
This allowed me to subsequently extend the class to create my Store.
IStorable.java
public interface IStorable {
String getUuid();
}
Store.java
public class Store<T extends IStorable> extends LinkedHashMap<String, T> implements ISaveable {
// GSON & Location
private transient Gson gson;
private transient Path location;
private transient Type type;
/**
* Constructs a new store.
*
* #param gson The gson to use for saving and loading.
* #param location The location of the JSON file.
* #param generic The generic that this instance of this class is using (due to type erasure).
*/
public Store(Gson gson, Path location, Class<T> generic) {
this.gson = gson;
this.location = location;
this.type = TypeToken.getParameterized(Store.class, generic).getType();
}
// Putting
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> putAll(T... values) {
for (T value : values) {
this.put(value.getUuid(), value);
}
return this;
}
// Replacing
public Store<T> replace(T value) {
this.replace(value.getUuid(), value);
return this;
}
// Removing
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
// Implement ISaveable
#Override
public Gson getGson() {
return gson;
}
#Override
public Path getLocation() {
return location;
}
#Override
public Type getType() {
return type;
}
// Setters
public void setLocation(Path location) {
this.location = location;
}
}

getting net.sf.json.JSONException: java.lang.reflect.InvocationTargetException

i have the following method
public static <E> APIGatewayProxyResponseEvent generateResponse(E request, E response, int statusCode){
JSONObject result = new JSONObject();
result.put(Constants.REQUEST, request);
result.put(Constants.RESPONSE, response);
return new APIGatewayProxyResponseEvent()
.withBody(result.toString())
.withStatusCode(statusCode)
.withHeaders(Constants.commonHeaders);
}
i am getting net.sf.json.JSONException: java.lang.reflect.InvocationTargetException when result.put(Constants.RESPONSE, response); is executed
response is
Also the corresponding class is:
public class PhysicalMediaURL extends MediaURL {
private static final String IDENTIFIER_PREFIX = "images/I/";
public PhysicalMediaURL(String physicalId, String extension, MediaHostnameProvider mediaHostnameProvider) {
super("images/I/" + physicalId, extension, mediaHostnameProvider);
}
}
public abstract class MediaURL implements URL {
private final String identifier;
private final String extension;
private final MediaHostnameProvider mediaHostnameProvider;
public MediaURL(String identifier, String extension, MediaHostnameProvider mediaHostnameProvider) {
this.identifier = identifier;
this.extension = extension;
this.mediaHostnameProvider = mediaHostnameProvider;
}
public String getIdentifier() {
return this.identifier;
}
public String getExtension() {
return this.extension;
}
public String getDomainName() {
return this.mediaHostnameProvider.getMediaHostname(this.getExtension());
}
public String getURL() {
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append("https://");
urlBuilder.append(this.getDomainName());
urlBuilder.append('/');
urlBuilder.append(this.getIdentifier());
urlBuilder.append('.');
urlBuilder.append(this.getExtension());
return urlBuilder.toString();
}
public List<String> getStyleTags() {
return null;
}
}
where PhysicalMediaURL is of type: URL and that is an interface
public interface URL {
String getIdentifier();
String getDomainName();
String getExtension();
List<String> getStyleTags();
String getURL();
}
I am a bit stuck in this.. need help.
First off it looks like you are using a JSON implementation that is not updated as regularly as the other ones(Your exception is from net.sf.json). I always recommend using the org.json implementation as it receives regular updates and bugfixes.
Most implementations of JSONObject, when used in this form, use bean based reflection to retrieve values from your object. This is not always what you want when your object is in an inheritance hierarchy because, depending on the object and the JSONObject impl, it will pull fields from the implementation that are not on your higher level type(URL in this case).
If you really want a generic serialization function use something like Jackson or Gson that will allow you to specify the type as a part of the serialization. Otherwise consider transforming your objects, before they are passed to your generateResponse function, into simpler objects such as a Map<String, String> that can serialize unambiguously.
As a final thought JSONObject's generic serialization works, but, its performance is likely to be worse than using a dedicated higher level serializer like Jackson. It's best used with the explicit put methods to generate simple objects.

Graphql SPQR customize object serialization / deserialization

I have the following data model with custom attributes:
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
}
class AdditionalAttribute {
private Key key;
private String value;
}
class Key {
private String name;
private Class<?> type;
}
My model produces this json:
{"id":123, "attributes": [{"key1":12345}, {"key2":"value2"}]}
My expected json is:
{"id":123, "key1":12345, "key2":"value2"}
How can I achieve a such serialization / deserialization using graphql spqr?
FYI, currently I can do it in REST API with jackson (BeanSerializerModifier for serialization and #JsonAnySetter for deserialization) as follow:
// Serialization using BeanSerializerModifier
class FooModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(
SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter writer = beanProperties.get(i);
if (Foo.class.isAssignableFrom(beanDesc.getBeanClass()) && "attributes".equals(writer.getName())) {
beanProperties.set(i, new FooAttributesWriter(writer));
}
}
return beanProperties;
}
}
class FooAttributesWriter extends BeanPropertyWriter {
public HasAttributesWriter(BeanPropertyWriter w) {
super(w);
}
#Override
public void serializeAsField(Object bean, JsonGenerator gen,
SerializerProvider prov) throws Exception {
if(Foo.class.isAssignableFrom(bean.getClass())) {
Set<AdditionalAttribute> set = ((Foo) bean).getAttributes();
for (AdditionalAttribute a : set) {
gen.writeStringField(a.getKey().getName(), a.getValue());
}
}
}
}
// Deserilization using #JsonAnySetter
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
// Deserialization of custom properties
#JsonAnySetter
public void set(String name, Object value) {
attributes.add(new AdditionalAttribute(buildKey(name,value), value));
}
}
The problem here is not JSON (de)serialization. With GraphQL, the shape of all your inputs and outputs is defined by the schema, and the schema can not normally have dynamic parts (object types where the fields are unknown ahead of time). Because your Set<AdditionalAttribute> can contain anything at all at runtime, it means your Foo type would have to have unknown fields. This is highly antithetical to how GraphQL is designed.
The only way to achieve a dynamic structure is to have an object scalar which effectively is a JSON blob that can not be validated, or sub-selected from. You could turn Foo into such a scalar by adding #GraphQLScalar to it. Then all input would be valid, {"id":123, "key1":12345 "key2":"value2"} but also {"whatever": "something"}. And it would be your logic's job to ensure correctness. Additionally, if you ever return Foo, the client would not be able to sub-select from it. E.g. {foo} would be possible but {foo { id }} would not, because the schema would no longer know if the id field is present.
To recap, you options are:
Leaving it as it is (the dynamic stuff is a list nested under attributes)
Turning Set<AdditionalAttribute> into a type (a new class or EnumMap) with known structure with all the possible keys as fields. This is only possible if the keys aren't totally dynamic
Making the whole enclosing object an object scalar by using #GraphQLScalar
Thanks a lot for your time and the proposed options.
Currently, we have found another way (maybe option 4 :) ) to generate a "similar" json to the expected output (We lost the type information in the generated output, but we have another logic that helps us to retrieve the type).
Here an example :
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
#GraphQLQuery
public String get(#GraphQLArgument(name = "key") String key) {
for (AdditionalAttribute a : attributes) {
if (a.getConfigurationKey().getKey().equalsIgnoreCase(key)) {
return a.getAttributeValue();
}
}
return null;
}
and we can sub-select Foo as follow:
foo {
id
key1: get(key: "key1")
key2: get(key: "key2")
}
And this return
{"id":123, "key1":"12345", "key2":"value2"}

Deserialize JSON object from MongoDB to Java object on GET request

I have some nested classes in Java, simplified here. Getters and setters exist.
Example
public class Planet {
#JsonProperty("name")
private String name;
#JsonProperty("moons")
private List<Moon> moons;
}
public class Moon {
#JsonProperty("moonname")
private String name;
#JsonProperty("craters")
private int craters;
}
I want to be able to deserialize the records on mongo (following this same structure) to java objects on the rest controller, specifically the HTTP GET request.
#RestController
#RequestMapping("/planets")
public class PlanetController {
#Autowired
private PlanetService planetService;
#RequestMapping("/")
public List<Planet> getAllPlanets() {
//Need to deserialize here
return planetService.getAll();
}
#RequestMapping("/{name}")
public Planet getItemsWithName(#PathVariable("name") String name) {
//deserialize here
return planetService.getEntryWithName(name.toLowerCase());
}
PlanetService.getAll() is expecting return type of List. getEntryWithName() is expecting return type of Planet.
How can I loop the results in the getAll() so I can deserialize them before they are returned?
Using Jackson's object mapper, I can do the serialization of a Java object to a JSON object.
ObjectMapper mapper = new ObjectMapper();
try {
mapper.writeValue(new File("target/mars.json"), mars);
} catch (IOException e) {
e.printStackTrace();
}
I can probably use readValue for the opposite process but I don't know how to loop the results.
I will appreciate the help. Let me know if something is not clear.
public List<Planet> getAllPlanets() {
List<Planet> planets = planetService.getAll();
String jsonString = new ObjectMapper().writeValueAsString(planets);
return planets;
}

Read-only field in GSON serialization [duplicate]

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.

Categories

Resources