google / gson

A Java serialization/deserialization library to convert Java Objects into JSON and back
Apache License 2.0
23.26k stars 4.27k forks source link

Read and write Json properties using methods (ie. getters & setters) #232

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
if your object has an int getId(); method. Maybe you want to have a 

@Field("id")
public int getId();

When the Json is generated the getId() method is called and the result is 
placed in "id"
I know that I could have a int field in my class, but, in my case the Id is 
generate by Hibernate once the object is saved.

Original issue reported on code.google.com by Germanattanasio on 7 Sep 2010 at 2:04

GoogleCodeExporter commented 9 years ago
This will be extremely useful for synthetic fields and for type hierarchies. 
I.e. say you have a base class with an Id field that you want to serialize with 
a type specific name. You could have the Id be transient and have a getId 
method that child classes could override to return their Id types etc.

Original comment by nfiedel on 17 Sep 2010 at 5:58

GoogleCodeExporter commented 9 years ago

Original comment by limpbizkit on 6 Oct 2010 at 5:38

GoogleCodeExporter commented 9 years ago
Issue 185 has been merged into this issue.

Original comment by limpbizkit on 6 Oct 2010 at 6:09

GoogleCodeExporter commented 9 years ago

Original comment by limpbizkit on 6 Oct 2010 at 6:10

GoogleCodeExporter commented 9 years ago

Original comment by inder123 on 3 Nov 2010 at 12:25

GoogleCodeExporter commented 9 years ago
It would be nice if GSON used getters and setters, actually it is a bit 
confusing to use as one cannot transform data in setters.

Original comment by astronau...@gmail.com on 25 May 2011 at 9:36

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
Hi! Actually it is not very hard to check out sources of GSON and add "use 
getters" ability. I required this in order to be able to work with Hibernate 
proxies seamlessly ... if anybody is interested, mail me to 
jakub.gemrot@gmail.com (I'm sorry I don't have time to create patches right 
now...).

The key place to patch is JsonSerializationVisitor.visitFieldUsingCustomHandler(
      FieldAttributes f, Type declaredTypeOfField, Object parent)

Look for:

      obj = f.get(parent);

Than just replace with:

      Object obj = null;
      if (useGetters) {
          String fieldName = f.getName();
          if (fieldName == null || fieldName.length() == 0) throw new RuntimeException("fieldName is invalid!");
          String getter = null;
          if (declaredTypeOfField == boolean.class) {
              getter = "is";
          } else
          if (declaredTypeOfField == Boolean.class) {
              getter = "get"; // should be get... but some frameworks will put "is" ... checked later if this is not found...
          } else {
              getter = "get";
          }
          getter += fieldName.substring(0, 1).toUpperCase() + (fieldName.length() <= 1 ? "" : fieldName.substring(1));
          Method method = null;
          try {
              method = parent.getClass().getMethod(getter);
          } catch (Exception e) {
              // no getter present
              if (declaredTypeOfField == Boolean.class) {
                  // getter might start with "get" not "is"
                  getter = "is" + fieldName.substring(0, 1).toUpperCase() + (fieldName.length() <= 1 ? "" : fieldName.substring(1));
                  try {
                      method = parent.getClass().getMethod(getter);
                  } catch (Exception e2) {
                    // no, event "is" prefixed getter for Boolean.class does not exist
                  }
              }
          }
          if (method == null) {
              obj = f.get(parent);
          } else {
              try {               
                obj = method.invoke(parent);
            } catch (Exception e) {             
                throw new RuntimeException("Could not invoke getter: " + obj.getClass() + "." + getter + "() !", e);
            }  
          }              
      } else {      
          obj = f.get(parent);
      }

See how boolean.class & Boolean.class are handled, I know it looks wierd. The 
trick is that you have to take care of boolean.class as well as Boolean.class. 
Eclipse is suggesting that "Boolean" bean properties should use getters 
prefixed with "get" but Hibernate is using "is". So that case must be treated 
extra.

Cheers!

Jimmy

Original comment by Jakub.Ge...@gmail.com on 18 Jun 2011 at 3:38

GoogleCodeExporter commented 9 years ago
Oh, and watch out for failing tests inside DefaultTypeAdaptersTest working with 
Date/Timestamp/etc... just comment them out :)

Best, Jimmy

Original comment by Jakub.Ge...@gmail.com on 18 Jun 2011 at 3:38

GoogleCodeExporter commented 9 years ago
If you wish to test your code after that, you may use this (it's not actually a 
test case ... but it will print serialized Gson that will contain different 
values than actually stored due to the getter definition):

  public static class MyUtilClass {

      private int util = 5;

      public MyUtilClass() {

      }

      public MyUtilClass(int util) {
          this.util = util;
      }

    public int getUtil() {
        return util * 10;
    }

    public void setUtil(int util) {
        this.util = util * 10;
    }

  }

  public static class MyClass {

      private int value = 10;

      private MyUtilClass cls = new MyUtilClass();

      private MyUtilClass[] clss = new MyUtilClass[3];

      private List<MyUtilClass> list = new ArrayList<MyUtilClass>(10);

      private Map<Integer, MyUtilClass> map = new HashMap<Integer, MyUtilClass>();

      private Set<MyUtilClass> set = new HashSet<MyUtilClass>();

      private Boolean b1 = true;

      private boolean b2 = true;

      public MyClass() {
          clss[0] = new MyUtilClass(1);
          clss[2] = new MyUtilClass(2);
          list.add(new MyUtilClass(3));
          list.add(new MyUtilClass(4));
          map.put(1, new MyUtilClass(5));
          map.put(2, new MyUtilClass(6));
          set.add(new MyUtilClass(7));
          set.add(new MyUtilClass(8));
      }

      public int getValue() {
          return value * 10;
      }

      public void setValue(int value) {
          this.value = value * 100;
      }

    public Boolean isB1() {
        return false;
    }

    public boolean isB2() {
        return false;
    }

  }

  public void testClassSerialization() {
      GsonBuilder builder = new GsonBuilder();
      builder.setUseGetters(true);
      builder.setPrettyPrinting();
      gson = builder.create();    
      MyClass obj = new MyClass();
      String json = gson.toJson(obj);
      System.out.println(json);
  }

Best,
Jimmy

Original comment by Jakub.Ge...@gmail.com on 18 Jun 2011 at 3:42

GoogleCodeExporter commented 9 years ago
In Gson 2.1, it's possible to do this as an extension with a TypeAdapterFactory 
based on ReflectiveTypeAdapterFactory. We may want to provide an extension.

Original comment by limpbizkit on 29 Dec 2011 at 5:36

GoogleCodeExporter commented 9 years ago
Issue 285 has been merged into this issue.

Original comment by limpbizkit on 29 Dec 2011 at 5:52

GoogleCodeExporter commented 9 years ago
I think this implementation is good, except I suggest make configurable to 
throw an exception or not. If getter is not found, it maybe treated as this 
field is not exposed. This is the common case, why getter/setter not provided 
for a field.

Second thing: I should swap the two process way of Boolean class, i think "is" 
prefix is more common than "get" for these fields.

Original comment by h...@hron.me on 11 Mar 2012 at 3:54

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
I made a small code to support bind by getter/setter method or field.
Here is the sample code:

        GsonBuilder bsonBuilder = new GsonBuilder();
        Gson gson = bsonBuilder.setFieldBindingStrategy(FieldBindingStrategy.GET_METHOD).create();
        // or by field modifier: gson = bsonBuilder.setFieldBindingStrategy(FieldBindingStrategy.FieldModifier.valueOf(Modifier.PRIVATE+Modifier.PROTECTED)).create();

Original comment by 70l...@gmail.com on 21 Feb 2014 at 7:27

Attachments:

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
one more suggestion: comparing to fastJson or jackson, the performance is a bit 
slower. I think performance of gson should be improved continually.

Original comment by 70l...@gmail.com on 21 Feb 2014 at 7:30

jansohn commented 8 years ago

This would be very helpful when working with JavaFX properties, e.g. javafx.beans.property.StringProperty, as these properties cannot be instantiated with an empty constructor.

schlm3 commented 8 years ago

GSon is useless for me without this feature. Wanted to use it for client server communication together with a GWT app while using a common Data interface. The point is, that the server already has it's very own object model and likes only to provide a view for the json serialisation which should look like the data required inside the GWT app. Would have worked perfectly if gson had supported getter/setter like serialisation.

rajsrivastav1919 commented 7 years ago

I have the same requirement, I want Gson to use setters for setting the field instead of reflection. I am deserializing a thrift object's json (which does not contain isSet vector details). If setter methods were called, isSet vector will be formed automatically. (It can be argued that isSet vector should have been present in the json in the first place. Agreed, but it is an internal field for use by thrift and thrift takes the pain of maintaining it every time some fields are edited.)

With current Gson implementation, thrift objects can't be deserialized "effectively". Other JSON & XML deserializing libraries (like Jackson, CastorXML) call setters, and hence the objects formed are compatible with thrift. For now, I am using Gson by making changes in it for calling setter methods instead of using reflection.

From the thread above, I see that you don't want to give native support in Gson for this feature. My suggestion is to add a flag in GsonBuilder which indicates Gson to use getter/setter methods. This will be set to false by default so the current functionality does not change. If set to true, Gson will try to call getter/setter methods if present, otherwise fallback to reflection for getting/setting the values. This will make Gson compatible to be used for Thrift objects also.

For 99% POJO classes current implementation works I agree. But, having as an optional feature shouldn't harm anyone I guess.

inder123 commented 7 years ago

@rajsrivastav1919 Would be great for someone to fork Gson with support for this. As we understand the solution better, we will consider adding it.

rajsrivastav1919 commented 7 years ago

Thanks @inder123 for the consideration. I will try to point out some genuine use cases for this if I find any.

Meanwhile, here is how I have been using it till now in various projects in my company. If you have some suggestions regarding the approach, I'll be glad to incorporate them.

tobilarscheid commented 7 years ago

Hi, let me add to this and outline our teams desired use case for this feature:

We came to use gson as the couchdb java driver uses gson. Every POJO you want to store is serialized by gson. What we would like to do is add a generic interface that contains the standard fields (i.e. their getters) that every object in our db needs (Think revision, last updated, stuff like this). Unfortunately this is not possible with gson not being able to serialize based on getter/setter methods.

erik777 commented 7 years ago

Please make this a priority. I have a case where the base object has a property that subclasses override and do not want to permit incoming data to change. I find it ironic that getters/setters are rendered useless with Gson. These things do not work:

protected final Doctype doctype = Doctype.Reg;

OK with Java. Not OK with Gson:

Caused by: java.lang.IllegalArgumentException: class com.blah.Subclass declares multiple JSON fields named doctype

@Override
public void setDoctype(Doctype doctype) {
    // Ignore
}

Ignored by Gson.

@Override
public Doctype getDoctype() {
    return Doctype.Reg;   // immutable
}

Also ignored by Gson.

There is no elegant work-around to protect the data from overwrite by fromJson. Your only option seems to be to overwrite the property in the client code after executing fromJson. This undoes sacred contracts of OO programming.

HUGE BUMP!

If anyone forks this please come back here and comment. I'd personally be happy with a getter/setter only implementation because the security holes the current Gson introduces is pretty significant in today's rapidly growing JSON based API universe. You have no idea what data is coming in from external consumers of your API, and you have no natural OO way to protect internal data.

rajsrivastav1919 commented 7 years ago

@erik777 Here is an implementation for the same if you want to use getter/setter methods. I've added a flag to tell Gson whether to use getter/setter methods or work in the default way.

Though I agree with @inder123 that ideally Gson should be defaulting to using reflection to set fields. Otherwise, it won't be a pure Gson serialisation/deserialisation library (which it is meant for). An object serialised via Gson should exactly give the same object when deserialised with it, which will not be always true if using getter/setter methods. Hence, only getter/setter implementation is not suitable here. Still, I would appreciate if Gson at least decides to include flag based approach in their code, because in some or other case, its useful.

james-hu commented 6 years ago

Is it possible to have @rajsrivastav1919 's implementation into the code base? Besides, it might also be useful if setAccessible(true) code can be opt-out by configuration.

TheLoneKing commented 6 years ago

I need this feature too. Any updates on this. I see an open pull request but there hasn't been much improvement on that either.

rajsrivastav1919 commented 6 years ago

@TheLoneKing I have not made any updates there because there hasn't been any decision to include it. In case it is decided to approve these changes, I will make the changes as per the latest code at that time.

mattbushell commented 5 years ago

I need this feature; i know i could use a TypeAdapter, but this option would be more succinct

Usecase normalising data I do not control:

public class SomeResponse {
    @SerializedName("SomeObject")
    Map<Integer, SomeObject> idSomeObjectMap;

    public void setIdSomeObjectMap(Map<Integer, SomeObject> idSomeObjectMap) {
        this.idSomeObjectMap = idSomeObjectMap;

        //need to put ids on objects
        idSomeObjectMap.entrySet().stream().forEach(me -> me.getValue().setSomeObjectID(me.getKey()));
    }

    public static class SomeObject {
        Integer someObjectsID;

        @SerializedName("N")
        String someObjectsName;

        public void setSomeObjectID(Integer someObjectID) {
            this.someObjectsID = someObjectID;
        }
    }
}

Response for example:

"SomeObject": {
      "173": {
        "N": "SomeObject has a name"
      },
      "246": {
        "N": "SomeObject also has a name"
      }
}
onacit commented 4 years ago

Is this still in progress...???

aakashchauhan71 commented 1 year ago

I also require this feature.