181

This might be a duplicate. But I cannot find a solution to my Problem.

I have a class

public class MyResponse implements Serializable {

    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
}

Getters and setters are generated by Eclipse.

In another class, I set the value to true, and write it as a JSON string.

System.out.println(new ObjectMapper().writeValueAsString(myResponse));

In JSON, the key is coming as {"success": true}.

I want the key as isSuccess itself. Is Jackson using the setter method while serializing? How do I make the key the field name itself?

Kingpin2k
  • 47,277
  • 10
  • 78
  • 96
iCode
  • 8,892
  • 21
  • 57
  • 91

12 Answers12

201

This is a slightly late answer, but may be useful for anyone else coming to this page.

A simple solution to changing the name that Jackson will use for when serializing to JSON is to use the @JsonProperty annotation, so your example would become:

public class MyResponse implements Serializable {

    private boolean isSuccess;

    @JsonProperty(value="isSuccess")        
    public boolean isSuccess() {
        return isSuccess;
    }

    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
}

This would then be serialised to JSON as {"isSuccess":true}, but has the advantage of not having to modify your getter method name.

Note that in this case you could also write the annotation as @JsonProperty("isSuccess") as it only has the single value element

Scott
  • 2,179
  • 1
  • 10
  • 6
37

I recently ran into this issue and this is what I found. Jackson will inspect any class that you pass to it for getters and setters, and use those methods for serialization and deserialization. What follows "get", "is" and "set" in those methods will be used as the key for the JSON field ("isValid" for getIsValid and setIsValid).

public class JacksonExample {   

    private boolean isValid = false;

    public boolean getIsValid() {
        return isValid;
    }

    public void setIsValid(boolean isValid) {
        this.isValid = isValid;
    }
} 

Similarly "isSuccess" will become "success", unless renamed to "isIsSuccess" or "getIsSuccess"

Read more here: http://www.citrine.io/blog/2015/5/20/jackson-json-processor

Utkarsha Padhye
  • 407
  • 5
  • 8
  • 9
    isValid is not right naming convention for boolean data type in java. should be valid and isValid(), setValid() – vels4j Feb 01 '17 at 14:41
  • 3
    but isn't it supposed to be exactly that ? A convention ? If it does exist, could you link to Jackson reference that says it uses getter names as the JSON fields ? Or do you think it's a bad design choice ? – Abhinav Vishak Jun 28 '17 at 22:54
  • 2
    I wish there were a warning for this – RyPope Aug 12 '17 at 19:15
  • 1
    @vels4j Naming conventions go out the window when you're dealing with highly specific implementations. – Dragas Apr 03 '20 at 11:06
31

Using both annotations below, forces the output JSON to include is_xxx:

@get:JsonProperty("is_something")
@param:JsonProperty("is_something")
Fabio
  • 1,757
  • 19
  • 33
18

When you are using Kotlin and data classes:

data class Dto(
    @get:JsonProperty("isSuccess") val isSuccess: Boolean
)

You might need to add @param:JsonProperty("isSuccess") if you are going to deserialize JSON as well.

EDIT: If you are using swagger-annotations to generate documentation, the property will be marked as readOnly when using @get:JsonProperty. In order to solve this, you can do:

@JsonAutoDetect(isGetterVisibility = JsonAutoDetect.Visibility.NONE)
data class Dto(
    @field:JsonProperty(value = "isSuccess") val isSuccess: Boolean
)
Eirik Fosse
  • 569
  • 5
  • 7
9

You can configure your ObjectMapper as follows:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
            @Override
            public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
            {
                if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
                        && method.getName().startsWith("is")) {
                    return method.getName();
                }
                return super.nameForGetterMethod(config, method, defaultName);
            }
        });
burak emre
  • 1,501
  • 4
  • 22
  • 46
  • 1
    I like that you're trying to solve this via configuration. However, this will only work if you *always* prefix your boolean fields and JSON properties with "is". Say you have another boolean field simply named "enabled" which you want to serialize as such. Because the generated method is "isEnabled()", the above code will then serialize it to "isEnabled" instead of just "enabled". Ultimately, the problem is that for both fields "x" and "isX", Eclipse generates method "isX()"; so you can't infer a property name matching the field. – David Siegal Jul 18 '19 at 00:54
  • @DavidSiegal base on burak answer I've extended the answer below to support such case. – edmundpie Nov 22 '19 at 18:06
6

I didn't want to mess with some custom naming strategies, nor re-creating some accessors.
The less code, the happier I am.

This did the trick for us :

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties({"success", "deleted"}) // <- Prevents serialization duplicates 
public class MyResponse {

    private String id;
    private @JsonProperty("isSuccess") boolean isSuccess; // <- Forces field name
    private @JsonProperty("isDeleted") boolean isDeleted;

}
Adrien
  • 71
  • 2
  • 2
3

Building upon Utkarsh's answer..

Getter names minus get/is is used as the JSON name.

public class Example{
    private String radcliffe; 

    public getHarryPotter(){
        return radcliffe; 
    }
}

is stored as { "harryPotter" : "whateverYouGaveHere" }


For Deserialization, Jackson checks against both the setter and the field name. For the Json String { "word1" : "example" }, both the below are valid.

public class Example{
    private String word1; 

    public setword2( String pqr){
        this.word1 = pqr; 
    }
}

public class Example2{
    private String word2; 

    public setWord1(String pqr){
        this.word2 = pqr ; 
    }
}

A more interesting question is which order Jackson considers for deserialization. If i try to deserialize { "word1" : "myName" } with

public class Example3{
    private String word1;
    private String word2; 

    public setWord1( String parameter){
        this.word2 = parameter ; 
    }
}

I did not test the above case, but it would be interesting to see the values of word1 & word2 ...

Note: I used drastically different names to emphasize which fields are required to be same.

Abhinav Vishak
  • 361
  • 1
  • 5
  • 17
2

You can change primitive boolean to java.lang.Boolean (+ use @JsonPropery)

@JsonProperty("isA")
private Boolean isA = false;

public Boolean getA() {
    return this.isA;
}

public void setA(Boolean a) {
    this.isA = a;
}

Worked excellent for me.

Reynard
  • 953
  • 13
  • 27
2

If you are interested in handling 3rd party classes not under your control (like @edmundpie mentioned in a comment) then you add Mixin classes to your ObjectMapper where the property/field names should match the ones from your 3rd party class:

public class MyStack32270422 {

  public static void main(String[] args) {
    ObjectMapper om3rdParty = new ObjectMapper();
    om3rdParty .addMixIn(My3rdPartyResponse.class, MixinMyResponse.class);
    // add further mixins if required
    String jsonString = om3rdParty.writeValueAsString(new My3rdPartyResponse());
    System.out.println(jsonString);
  }
}

class MixinMyResponse {
  // add all jackson annotations here you want to be used when handling My3rdPartyResponse classes
  @JsonProperty("isSuccess")
  private boolean isSuccess;
}

class My3rdPartyResponse{
  private boolean isSuccess = true;
  // getter and setter here if desired
}

Basically you add all your Jackson annotations to your Mixin classes as if you would own the class. In my opinion quite a nice solution as you don't have to mess around with checking method names starting with "is.." and so on.

1

there is another method for this problem.

just define a new sub-class extends PropertyNamingStrategy and pass it to ObjectMapper instance.

here is a code snippet may be help more:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
        @Override
        public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
            String input = defaultName;
            if(method.getName().startsWith("is")){
                input = method.getName();
            }

            //copy from LowerCaseWithUnderscoresStrategy
            if (input == null) return input; // garbage in, garbage out
            int length = input.length();
            StringBuilder result = new StringBuilder(length * 2);
            int resultLength = 0;
            boolean wasPrevTranslated = false;
            for (int i = 0; i < length; i++)
            {
                char c = input.charAt(i);
                if (i > 0 || c != '_') // skip first starting underscore
                {
                    if (Character.isUpperCase(c))
                    {
                        if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
                        {
                            result.append('_');
                            resultLength++;
                        }
                        c = Character.toLowerCase(c);
                        wasPrevTranslated = true;
                    }
                    else
                    {
                        wasPrevTranslated = false;
                    }
                    result.append(c);
                    resultLength++;
                }
            }
            return resultLength > 0 ? result.toString() : input;
        }
    });
Joseph-z
  • 29
  • 7
1

The accepted answer won't work for my case.

In my case, the class is not owned by me. The problematic class comes from 3rd party dependencies, so I can't just add @JsonProperty annotation in it.

To solve it, inspired by @burak answer above, I created a custom PropertyNamingStrategy as follow:

mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
  @Override
  public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
  {
    if (method.getParameterCount() == 1 &&
            (method.getRawParameterType(0) == Boolean.class || method.getRawParameterType(0) == boolean.class) &&
            method.getName().startsWith("set")) {

      Class<?> containingClass = method.getDeclaringClass();
      String potentialFieldName = "is" + method.getName().substring(3);

      try {
        containingClass.getDeclaredField(potentialFieldName);
        return potentialFieldName;
      } catch (NoSuchFieldException e) {
        // do nothing and fall through
      }
    }

    return super.nameForSetterMethod(config, method, defaultName);
  }

  @Override
  public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName)
  {
    if(method.hasReturnType() && (method.getRawReturnType() == Boolean.class || method.getRawReturnType() == boolean.class)
        && method.getName().startsWith("is")) {

      Class<?> containingClass = method.getDeclaringClass();
      String potentialFieldName = method.getName();

      try {
        containingClass.getDeclaredField(potentialFieldName);
        return potentialFieldName;
      } catch (NoSuchFieldException e) {
        // do nothing and fall through
      }
    }
    return super.nameForGetterMethod(config, method, defaultName);
  }
});

Basically what this does is, before serializing and deserializing, it checks in the target/source class which property name is present in the class, whether it is isEnabled or enabled property.

Based on that, the mapper will serialize and deserialize to the property name that is exist.

edmundpie
  • 1,111
  • 1
  • 13
  • 24
1

The most simple solution if you are also using Lombok is to annotate the property with @JsonProperty like so

@Getter
@Setter
public class MyResponse implements Serializable {

    @JsonProperty(value="isSuccess")
    private boolean isSuccess;

}