Jackson mengganti nama bidang boolean primitif dengan menghapus 'is'

93

Ini mungkin duplikat. Tetapi saya tidak dapat menemukan solusi untuk Masalah saya.

saya ada kelas

public class MyResponse implements Serializable {

    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

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

Getters dan setter dihasilkan oleh Eclipse.

Di kelas lain, saya menyetel nilainya menjadi true, dan menulisnya sebagai string JSON.

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

Di JSON, kuncinya datang sebagai {"success": true}.

Saya ingin kunci isSuccessitu sendiri. Apakah Jackson menggunakan metode penyetel saat serialisasi? Bagaimana cara membuat kunci menjadi nama field itu sendiri?

iCode
sumber
1
jika nama properti isSuccessAnda seperti nama metode Anda harus isIsSuccesssaya pikir
Jens
Saya mengerti. Saya pikir itu lebih baik SetSuccess karena dibuat oleh Eclipse. (Mengikuti standar)
iCode

Jawaban:

119

Ini adalah jawaban yang agak terlambat, tetapi mungkin berguna bagi siapa pun yang datang ke halaman ini.

Solusi sederhana untuk mengubah nama yang akan digunakan Jackson saat membuat serial ke JSON adalah dengan menggunakan anotasi @JsonProperty , jadi contoh Anda akan menjadi:

public class MyResponse implements Serializable {

    private boolean isSuccess;

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

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

Ini kemudian akan diserialkan ke JSON sebagai {"isSuccess":true}, tetapi memiliki keuntungan karena tidak harus mengubah nama metode pengambil Anda.

Perhatikan bahwa dalam kasus ini Anda juga dapat menulis anotasi karena @JsonProperty("isSuccess")hanya memiliki satu valueelemen

Scott
sumber
Metode ini tidak akan berfungsi untuk kasus saya karena kelas tersebut bukan milik saya karena berasal dari dependensi pihak ke-3. Untuk kasus seperti itu, lihat jawaban saya di bawah.
edmundpie
4
Saya menggunakan sepatu bot musim semi dengan jackson tetapi mendapatkan dua bidang, satu adalah "sukses" dan lainnya adalah "sukses" dan ketika saya menggunakan Boolean non primitif daripada hanya satu bidang "isSuccess"
Vishal Singla
@VishalSingla Saya memiliki masalah yang persis sama, solusi ini menghasilkan dua bidang di Spring Boot
Aron Fiechter
22

Saya baru-baru ini mengalami masalah ini dan inilah yang saya temukan. Jackson akan memeriksa setiap kelas yang Anda berikan untuk getter dan setter, dan menggunakan metode tersebut untuk serialisasi dan deserialization. Selanjutnya, "get", "adalah" dan "set" dalam metode tersebut akan digunakan sebagai kunci untuk kolom JSON ("isValid" untuk getIsValid dan setIsValid).

public class JacksonExample {   

    private boolean isValid = false;

    public boolean getIsValid() {
        return isValid;
    }

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

Demikian pula "isSuccess" akan menjadi "sukses", kecuali diganti namanya menjadi "isIsSuccess" atau "getIsSuccess"

Baca lebih lanjut di sini: http://www.citrine.io/blog/2015/5/20/jackson-json-processor

Utkarsha Padhye
sumber
6
isValid bukanlah konvensi penamaan yang benar untuk tipe data boolean di java. harus valid dan isValid (), setValid ()
vels4j
2
tapi bukankah seharusnya begitu? Sebuah konvensi? Jika memang ada, dapatkah Anda menautkan ke referensi Jackson yang mengatakan itu menggunakan nama pengambil sebagai bidang JSON? Atau menurut Anda itu pilihan desain yang buruk?
Abhinav Vishak
2
Saya berharap ada peringatan untuk ini
RyPope
@ vels4j Konvensi penamaan keluar jendela ketika Anda berurusan dengan implementasi yang sangat spesifik.
Dragas
13

Menggunakan kedua anotasi di bawah ini, memaksa keluaran JSON untuk menyertakan is_xxx:

@get:JsonProperty("is_something")
@param:JsonProperty("is_something")
Fabio
sumber
Ini adalah jawaban terbaik untuk pertanyaan ini.
dustinevan
1
Apakah itu Jawa? Mungkin itu Kotlin?
spottedmahn
5

Anda dapat mengonfigurasi Anda ObjectMappersebagai berikut:

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
sumber
1
Saya suka Anda mencoba menyelesaikan ini melalui konfigurasi. Namun, ini hanya akan berfungsi jika Anda selalu mengawali kolom boolean dan properti JSON dengan "adalah". Katakanlah Anda memiliki bidang boolean lain yang diberi nama "enabled" yang ingin Anda buat serialnya seperti itu. Karena metode yang dihasilkan adalah "isEnabled ()", kode di atas kemudian akan membuat serialisasi menjadi "isEnabled", bukan hanya "enabled". Akhirnya, masalahnya adalah untuk kedua kolom "x" dan "isX", Eclipse menghasilkan metode "isX ()"; sehingga Anda tidak dapat menyimpulkan nama properti yang cocok dengan bidang tersebut.
David Siegal
@DavidSiegal berdasarkan jawaban burak Saya telah memperpanjang jawaban di bawah ini untuk mendukung kasus tersebut.
edmundpie
4

Saat Anda menggunakan Kotlin dan kelas data:

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

Anda mungkin perlu menambahkan @param:JsonProperty("isSuccess")jika Anda juga akan melakukan deserialisasi JSON.

Eirik Fosse
sumber
2

Membangun di atas jawaban Utkarsh ..

Nama pengambil dikurangi get / digunakan sebagai nama JSON.

public class Example{
    private String radcliffe; 

    public getHarryPotter(){
        return radcliffe; 
    }
}

disimpan sebagai {"harryPotter": "everythingYouGaveHere"}


Untuk Deserialization, Jackson memeriksa penyetel dan nama field. Untuk Json String {"word1": "example"} , kedua hal di bawah ini 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 ; 
    }
}

Pertanyaan yang lebih menarik adalah urutan mana yang dipertimbangkan Jackson untuk deserialisasi. Jika saya mencoba untuk menghilangkan nama {"word1": "myName"} dengan

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

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

Saya tidak menguji kasus di atas, tetapi akan menarik untuk melihat nilai dari kata1 & kata2 ...

Catatan: Saya menggunakan nama yang sangat berbeda untuk menekankan bidang mana yang harus sama.

Abhinav Vishak
sumber
1

ada metode lain untuk masalah ini.

cukup definisikan sub-kelas baru extends PropertyNamingStrategy dan berikan ke instance ObjectMapper.

Berikut ini adalah potongan kode yang bisa membantu lebih banyak:

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
sumber
1

Saya tidak ingin mengacaukan beberapa strategi penamaan khusus, atau membuat ulang beberapa pengakses.
Semakin sedikit kode, semakin bahagia saya.

Ini melakukan trik untuk kami:

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
sumber
1

Jawaban yang diterima tidak akan berhasil untuk kasus saya.

Dalam kasus saya, kelas tersebut bukan milik saya. Kelas bermasalah berasal dari dependensi pihak ke-3, jadi saya tidak bisa hanya menambahkan @JsonPropertyanotasi di dalamnya.

Untuk mengatasinya, terinspirasi dari jawaban @burak di atas, saya membuat custom PropertyNamingStrategysebagai berikut:

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

Pada dasarnya apa yang dilakukan ini adalah, sebelum membuat serial dan deserialisasi, ia memeriksa di kelas target / sumber nama properti mana yang ada di kelas, apakah itu isEnabledatauenabled properti.

Berdasarkan itu, mapper akan membuat serialisasi dan deserialisasi ke nama properti yang ada.

edmundpie
sumber
0

Anda dapat mengubah boolean primitif menjadi java.lang.Boolean (+ penggunaan @JsonPropery)

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

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

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

Bekerja dengan sangat baik untuk saya.

Reynard
sumber