I am using Gson to serialize a custom object. I have defined an exclusion strategy as follows -
public class HumidityExclusionStrategy implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes f) {
System.out.println(f.getName());
boolean result = (f.getAnnotation(Expose.class) == null)
|| (f.getName().equalsIgnoreCase("humidity")
&& UserInputThread.shouldExcludeHumidity);
System.out.println(result);
return result;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
I initialize the Gson instance like this, in another class -
private static final Gson GSON = new GsonBuilder()
.setExclusionStrategies(new HumidityExclusionStrategy())
.create();
UserInputThread.shouldExcludeHumidity is a boolean that is modified based on some user input. When I use the Gson object to serialize for the first time (GSON.toJson(sensor)), the print statements in the shouldSkipField() method of the strategy do their work, and I get the expected output.
But when the value of UserInputThread.shouldExcludeHumidity changes, the result of the next serialization is the same as the first - the humidity field is included. Also, the print statements do not print anything to the console. Why is this happening ?
I have checked the value of the boolean UserInputThread.shouldExcludeHumidity, and it is what I expect it to be.
I found the reason for this problem. Internally, gson caches the result of an exclusion strategy using a map, in the Gson.java class.
In order to get the intended results, I had to initialize a new Gson object each time I needed to serialize. This solution might pose a performance issue, but for my case, performance is not really a problem and this method works.
Instead of creating a global Gson object and using it whenever necessary, I use the following method call -
private Gson getGson() {
return new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setExclusionStrategies(strat)
.create();
}
Old -
private final Gson gson = new .....;
public static void main(String[] args) {
..... gson.toJson(...); .....
}
New -
public static void main(String[] args) {
..... getGson().toJson(...); .....
}
Related
I'm new to java (Hum... No... I learned Java at school 10 years ago but never really used it since today).
I have an object class which corresponds to my json and was generated with the website http://www.jsonschema2pojo.org/ (simplified here) :
public class ServerDatasObject {
private Integer error;
private Boolean isOffline;
public Integer getError() {
return error;
}
public Boolean getIsOffline() {
return isOffline;
}
}
And another class used to access all object data (simplified too) :
public class ServerDatasHandler extends ServerDatasObject {
public ServerDatasHandler(String json) {
Gson gson = new Gson();
// how to populate current object using : gson.fromJson(json, ServerDatasObject.class);
}
}
The question is in the code: how to populate current object?
I searched and found something about InstanceCreator :
final Foo existing;
InstanceCreator<Foo> creator = new InstanceCreator<Foo>() {
public Foo createInstance(Type type) { return existing; }
}
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, creator).create();
Foo value = gson.fromJson(jsonString, Foo.class);
// value should be same as existing
but I don't understand how to use it and if it is what I need.
The final goal is to
{
ServerDatasHandler serverDatasHandler = new ServerDatasHandler (json);
do something with serverDatasHandler.getError()
}
Thanks for your help
You can create a separate static method which creates your handler from json:
public static ServerDatasHandler fromJsonConfig(String json) {
Gson gson = new Gson();
ServerDatasHandler handler = gson.fromJson(json, ServerDatasHandler.class);
return handler;
}
Of course you can also move this method to a factory.
I never used InstanceCreator inside constructor because parsing JSON is almost never a task for a constructor. Preferably it should be hidden inside framework or your own factories. Though, your example with InstanceCreator should also work if you will return ServerDatasHandler.this from the createInstance method.
When implementing dsljson in Java project, I see this is not correct. It is quite slow and hard to implement.
I create new object which implements from JsonObject
public static class abc implements JsonObject {
public final int x;
public final String s;
public abc(int x, String s) {
this.x = x;
this.s = s;
}
public void serialize(JsonWriter writer, boolean minimal) {
//parse the instance of object (abc) to json-string
}
public static final JsonReader.ReadJsonObject<abc> JSON_READER =
new JsonReader.ReadJsonObject<abc>() {
public abc deserialize(JsonReader reader) throws IOException {
// Use jsonreader and common json converter (numberconverter,
// stringconverter) to parse json-string to an
// instance of object (abc)
}
};
}
I create new : DslJson<Object> dslJson = new DslJson<Object>(); to call "deserialize"/"serialize" when use it.
I think, my implementation is not correct hence it is too slow.
So if you have any experiences or example on for this lib then can you help me provide your ideas about that?
Do we have another way to use dsljson?
DslJson cannot use like JackSon?
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{\"age\":33,\"messages\":[\"msg 1\",\"msg 2\"],
\"name\":\"mkyong\"}";
User user1 = mapper.readValue(jsonInString, User.class);
There are few examples in the library repository, eg: https://github.com/ngs-doo/dsl-json/blob/master/examples/Maven/src/main/java/com/dslplatform/maven/Example.java#L82
Some things to try:
reuse dslJson instance - it's costly to recreate it multiple times during testing
avoid using Strings - byte[] or Streams are much more GC friendly data types
If DslJson is not the fastest, most likely there is something wrong with your setup :) (which means you should show more code - how exactly are you testing/benching library)
This is an example of the kind JSON I'm trying to consume using GSON:
{
"person": {
"name": "Philip"
"father.name": "Yancy"
}
}
I was wondering if it were possible to deserialize this JSON into the following structure:
public class Person
{
private String name;
private Father father;
}
public class Father
{
private String name;
}
So that:
p.name == "Philip"
p.father.name == "Yancy"
Currently I am using #SerializedName to obtain property names containing a period, e.g.:
public class Person
{
private String name;
#SerializedName("father.name")
private String fathersName;
}
However, that's not ideal.
From looking at the documentation it doesn't appear to be immediately possible but there may be something I have missed - I'm new to using GSON.
Unfortunately I cannot change the JSON I'm consuming and I'm reluctant to switch to another JSON parsing library.
As far as I understand you can't do it in a direct way, because Gson will understand father.name as a single field.
You need to write your own Custom Deserializer. See Gson user's guide instructions here.
I've never tried it, but it doesn't seem to be too difficult. This post could be also helpful.
Taking a look at Gson's user guide and the code in that post, you'll need something like this:
private class PersonDeserializer implements JsonDeserializer<Person> {
#Override
public Person deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jobject = (JsonObject) json;
Father father = new Father(jobject.get("father.name").getAsString());
return new Person(jobject.get("name").getAsString(), father);
}
}
Assuming that you have suitable constructors...
And then:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Person.class, new PersonDeserializer());
Gson gson = gsonBuilder.create();
Person person = gson.fromJson(jsonString, Person.class);
And Gson will call your deserializer in order to deserialize the JSON into a Person object.
Note: I didn't try this code, but it should be like this or something very similar.
I couldn't do this with just Gson. I need a new library 'JsonPath'. I used Jackson's ObjectMapper to convert the object to string but you can easily use Gson for this.
public static String getProperty(Object obj, String prop) {
try {
return JsonPath.read(new ObjectMapper().writeValueAsString(obj), prop).toString();
} catch (JsonProcessingException|PathNotFoundException ex) {
return "";
}
}
// 2 dependencies needed:
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
// https://mvnrepository.com/artifact/com.jayway.jsonpath/json-path
// usage:
String motherName = getProperty(new Person(), "family.mother.name");
// The Jackson can be easily replaced with Gson:
new Gson().toJson(obj)
GSON appears to be doing some kind of trick where it looks at the internal fields of my JavaBeans instead of using the publically-accessible property information. Unfortunately this won't fly for us because our magically-created beans are full of private fields which I don't want it to store off.
#Test
public void testJson() throws Exception
{
Player player = new MagicPlayer(); //BeanUtils.createDefault(Player.class);
player.setName("Alice");
Gson gson = new GsonBuilder()
.registerTypeAdapter(Player.class, new PlayerTypeAdapter())
.create();
System.out.println(gson.toJson(bean));
}
private static class PlayerTypeAdapter implements JsonSerializer<Player>
{
#Override
public JsonElement serialize(Player player, Type type,
JsonSerializationContext context)
{
throw new RuntimeException("I got called, woohoo");
}
}
public static interface Player //extends SupportsPropertyChanges
{
public String getName();
public void setName(String name);
}
// Simple implementation simulating what we're doing.
public static class MagicPlayer implements Player
{
private final String privateStuff = "secret";
private String name;
#Override
public String getName()
{
return name;
}
#Override
public void setName(String name)
{
this.name = name;
}
}
This gives:
{"privateStuff":"secret","name":"Alice"}
And of course, never calls my type adapter, which seemingly makes it impossible to get any other behaviour.
I had the same issue, with version 2.3.1 of Gson. I got it working by using
.registerTypeHierarchyAdapter(Player.class, new PlayerTypeAdapter())
instead of
.registerTypeAdapter(Player.class, new PlayerTypeAdapter())
Current release of GSON does not work this way. This will serialize your type using the adapter.
gson.toJson(bean, Player.class)
Alternatively you can register the PlayerTypeAdapter for MagicPlayer.class and your existing code will work. GSON looks up the TypeAdapter by the concrete (or top level) type only. So you will have to register a custom type adapter for every concrete type you may encounter.
There is TypeHeirarchyAdapter for when you want the same TypeAdapter to be used for all extending classes of a given type. I have not had good luck with this though and have not really spent time looking into why it has not worked well for me. Here is some relevant discussion: https://groups.google.com/forum/?fromgroups=#!searchin/google-gson/interface/google-gson/0ebOxifqwb0/OXSCNTiLpBQJ
Why it does not work:
PlayerTypeAdapter should implements JsonSerializer. Does it?
If it does - than should work. It work perfect for me, so don't see reason why it does not work for you. Start you application in debug, put breakpoints and checkout. There should be some reason.
In your test where is Gson builder with adapter? You should change it:
public void testJson() throws Exception
{
Player player = new MagicPlayer(); //BeanUtils.createDefault(Player.class);
player.setName("Alice");
// this is wrong if you want adapter
/* Gson gson = new Gson(); */
// this is what you nead
Gson gson = new GsonBuilder()
.registerTypeAdapter(Player.class, new PlayerTypeAdapter())
.create();
System.out.println(gson.toJson(bean));
}
Does anyone know how to seralize a Runnable object using Gson's Instance Creator?
Thanks,
Kevin
As mentioned, the Instance Creator feature is for deserialization, not serialization. Also, use of an Instance Creator to deserialize a Runnable is not necessary.
Following is an example of using Gson to serialize and deserialize a Runnable instance.
import com.google.gson.Gson;
public class GsonFoo
{
public static void main(String[] args)
{
BarRun runRunRun = new BarRun();
runRunRun.name = "Whiskey";
runRunRun.state = 42;
String json = new Gson().toJson(runRunRun);
System.out.println(json);
// output: {"name":"Whiskey","state":42}
BarRun runCopy = new Gson().fromJson(json, BarRun.class);
System.out.println(runCopy.name); // Whiskey
System.out.println(runCopy.state); // 42
}
}
class BarRun implements Runnable
{
String name;
int state;
#Override
public void run()
{
// do something useful
}
}
If something about what you're trying to achieve necessitates use of an Instance Creator, then note that examples are available in the Gson User Guide section on the subject, as well as in the InstanceCreator JavaDocs.