Jackson zmienia nazwę prymitywnego pola boolowskiego, usuwając „jest”

93

To może być duplikat. Ale nie mogę znaleźć rozwiązania mojego problemu.

mam klasę

public class MyResponse implements Serializable {

    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

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

Metody pobierające i ustawiające są generowane przez Eclipse.

W innej klasie ustawiam wartość na true i zapisuję ją jako ciąg JSON.

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

W JSON klucz pojawia się jako {"success": true}.

Chcę, żeby klucz był isSuccesssobą. Czy Jackson używa metody ustawiającej podczas serializacji? Jak nadać kluczowi samą nazwę pola?

iCode
źródło
1
jeśli nazwa twojej nieruchomości to liek isSuccess, nazwa metody musi być tak isIsSuccessmyślę
Jens
Rozumiem. Pomyślałem, że jest lepszy, SetSuccess ponieważ jest generowany przez Eclipse. (Zgodnie ze standardem)
iCode

Odpowiedzi:

119

To trochę późna odpowiedź, ale może być przydatna dla każdego, kto odwiedza tę stronę.

Prostym rozwiązaniem zmiany nazwy, której będzie używał Jackson podczas serializacji do formatu JSON, jest użycie adnotacji @JsonProperty , więc przykład będzie wyglądał następująco:

public class MyResponse implements Serializable {

    private boolean isSuccess;

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

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

Byłoby to następnie serializowane do formatu JSON jako {"isSuccess":true}, ale ma tę zaletę, że nie trzeba modyfikować nazwy metody pobierającej.

Zauważ, że w tym przypadku możesz również napisać adnotację, ponieważ @JsonProperty("isSuccess")ma ona tylko jeden valueelement

Scott
źródło
Ta metoda nie zadziała w moim przypadku, ponieważ klasa nie jest moją własnością, ponieważ pochodzi z zależności innych firm. W takim przypadku zobacz moją odpowiedź poniżej.
edmundpie
4
Używam buta sprężynowego z jacksonem, ale zdobycie dwóch pól to „sukces”, a drugie to „isSuccess”, a kiedy używam nieprymitywnych wartości logicznych, to tylko jedno pole „isSuccess”
Vishal Singla
@VishalSingla Mam dokładnie ten sam problem, to rozwiązanie generuje dwa pola w Spring Boot
Aron Fiechter
22

Niedawno natknąłem się na ten problem i oto, co znalazłem. Jackson sprawdzi każdą przekazaną do niej klasę pod kątem metod pobierających i ustawiających i użyje tych metod do serializacji i deserializacji. To, co następuje po wyrażeniach „get”, „is” i „set” w tych metodach, zostanie użyte jako klucz dla pola JSON („isValid” dla getIsValid i setIsValid).

public class JacksonExample {   

    private boolean isValid = false;

    public boolean getIsValid() {
        return isValid;
    }

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

Podobnie „isSuccess” stanie się „sukcesem”, chyba że zostanie zmieniona na „isIsSuccess” lub „getIsSuccess”

Przeczytaj więcej tutaj: http://www.citrine.io/blog/2015/5/20/jackson-json-processor

Utkarsha Padhye
źródło
6
isValid nie jest prawidłową konwencją nazewnictwa dla logicznych typów danych w java. powinien być prawidłowy i isValid (), setValid ()
vels4j
2
ale czy to nie powinno być dokładnie tak? Konwencja? Jeśli istnieje, czy możesz połączyć się z odniesieniem Jacksona, który mówi, że używa nazw pobierających jako pól JSON? A może uważasz, że to zły wybór projektowy?
Abhinav Vishak
2
Chciałbym, żeby było ostrzeżenie
RyPope
@ vels4j Konwencje nazewnictwa wychodzą poza okno, gdy masz do czynienia z wysoce szczegółowymi implementacjami.
Dragas
13

Użycie obu poniższych adnotacji wymusza uwzględnienie w wyjściowym formacie JSON is_xxx:

@get:JsonProperty("is_something")
@param:JsonProperty("is_something")
Fabio
źródło
To najlepsza odpowiedź na to pytanie.
dustinevan
1
Czy to Java? Może to Kotlin?
spottedmahn
5

Możesz skonfigurować ObjectMappernastępujące opcje:

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
źródło
1
Podoba mi się, że próbujesz rozwiązać ten problem poprzez konfigurację. Jednak zadziała to tylko wtedy, gdy zawsze będziesz poprzedzać pola boolowskie i właściwości JSON znakiem „is”. Załóżmy, że masz inne pole boolowskie o nazwie „włączone”, które chcesz serializować jako takie. Ponieważ wygenerowaną metodą jest „isEnabled ()”, powyższy kod będzie następnie serializował ją do „isEnabled” zamiast po prostu „enabled”. Ostatecznie problem polega na tym, że dla obu pól „x” i „isX” Eclipse generuje metodę „isX ()”; więc nie można wywnioskować nazwy właściwości pasującej do pola.
David Siegal
@DavidSiegal w oparciu o burak answer Rozszerzyłem odpowiedź poniżej, aby wesprzeć taki przypadek.
edmundpie
4

Gdy używasz Kotlin i klas danych:

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

Może być konieczne dodanie, @param:JsonProperty("isSuccess")jeśli zamierzasz również deserializować JSON.

Eirik Fosse
źródło
2

Opierając się na odpowiedzi Utkarsha ...

Nazwy metody pobierania minus get / jest używane jako nazwa JSON.

public class Example{
    private String radcliffe; 

    public getHarryPotter(){
        return radcliffe; 
    }
}

jest przechowywany jako {"harryPotter": "cokolwiekYouGaveHere"}


W przypadku deserializacji Jackson sprawdza zarówno ustawiającego, jak i nazwę pola. W przypadku ciągu Json {"word1": "example"} oba poniższe są prawidłowe.

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 ; 
    }
}

Bardziej interesującym pytaniem jest to, który porządek Jackson rozważa przy deserializacji. Jeśli spróbuję deserializować {"word1": "myName"} z

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

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

Nie testowałem powyższego przypadku, ale byłoby interesujące zobaczyć wartości word1 i word2 ...

Uwaga: użyłem drastycznie różnych nazw, aby podkreślić, które pola muszą być takie same.

Abhinav Vishak
źródło
1

istnieje inna metoda rozwiązania tego problemu.

wystarczy zdefiniować nową podklasę, która rozszerza PropertyNamingStrategy i przekazuje ją do instancji ObjectMapper.

tutaj fragment kodu może być bardziej pomocny:

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;
        }
    });
eric
źródło
1

Nie chciałem mieszać z niektórymi niestandardowymi strategiami nazewnictwa ani ponownie tworzyć niektórych akcesorów.
Im mniej kodu, tym jestem szczęśliwszy.

To załatwiło nam sprawę:

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
źródło
1

Zaakceptowana odpowiedź nie będzie działać w moim przypadku.

W moim przypadku klasa nie jest moją własnością. Problematyczna klasa pochodzi z zależności innych firm, więc nie mogę po prostu dodać@JsonProperty adnotacji.

Aby rozwiązać ten problem, zainspirowany powyższą odpowiedzią @burak, stworzyłem następujący zwyczaj PropertyNamingStrategy:

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);
  }
});

Zasadniczo co to robi to przed szeregowania i deserializacji, sprawdza się w docelowej klasie / Nazwa obiektu źródłowego, który jest obecny w klasie, czy jest isEnabledlubenabled nieruchomość.

Na tej podstawie program mapujący będzie serializował i deserializował do istniejącej nazwy właściwości.

edmundpie
źródło
0

Możesz zmienić pierwotną wartość logiczną na java.lang.Boolean (+ użyj @JsonPropery)

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

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

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

U mnie zadziałało doskonale.

Reynard
źródło