Jackson ile özel bir Serileştiriciyi nasıl kullanırım?


111

Jackson'ı kullanarak JSON'a serileştirmek istediğim iki Java sınıfım var:

public class User {
    public final int id;
    public final String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Item {
    public final int id;
    public final String itemNr;
    public final User createdBy;

    public Item(int id, String itemNr, User createdBy) {
        this.id = id;
        this.itemNr = itemNr;
        this.createdBy = createdBy;
    }
}

Bir Öğeyi bu JSON'a serileştirmek istiyorum:

{"id":7, "itemNr":"TEST", "createdBy":3}

Kullanıcı yalnızca id. Ayrıca tüm kullanıcı nesnelerini JSON'a serilize edebileceğim gibi:

{"id":3, "name": "Jonas", "email": "jonas@example.com"}

Sanırım bunun için özel bir serileştirici yazmam Itemve bununla denemem gerekiyor :

public class ItemSerializer extends JsonSerializer<Item> {

@Override
public void serialize(Item value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,
        JsonProcessingException {
    jgen.writeStartObject();
    jgen.writeNumberField("id", value.id);
    jgen.writeNumberField("itemNr", value.itemNr);
    jgen.writeNumberField("createdBy", value.user.id);
    jgen.writeEndObject();
}

}

JSON'u Jackson How-to: Custom Serializers'daki şu kodla serileştiriyorum :

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule", 
                                              new Version(1,0,0,null));
simpleModule.addSerializer(new ItemSerializer());
mapper.registerModule(simpleModule);
StringWriter writer = new StringWriter();
try {
    mapper.writeValue(writer, myItem);
} catch (JsonGenerationException e) {
    e.printStackTrace();
} catch (JsonMappingException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Ama şu hatayı alıyorum:

Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() (use alternative registration method?)
    at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62)
    at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54)
    at com.example.JsonTest.main(JsonTest.java:54)

Jackson ile özel bir Serileştiriciyi nasıl kullanabilirim?


Gson ile bunu şu şekilde yapardım:

public class UserAdapter implements JsonSerializer<User> {

    @Override 
    public JsonElement serialize(User src, java.lang.reflect.Type typeOfSrc,
            JsonSerializationContext context) {
        return new JsonPrimitive(src.id);
    }
}

    GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapter(User.class, new UserAdapter());
    Gson gson = builder.create();
    String json = gson.toJson(myItem);
    System.out.println("JSON: "+json);

Ama bunu Jackson ile yapmam gerekiyor çünkü Gson arayüzler için desteğe sahip değil.


Jackson'ın özel Serileştiricinizi nasıl / nerede kullanmasını sağladınız Item? Denetleyici yöntemimin standart bir serileştirilmiş nesne döndürdüğü bir sorun yaşıyorum TypeA, ancak başka bir belirli denetleyici yöntemi için, onu farklı şekilde serileştirmek istiyorum. Bu neye benzerdi?
Don Cheadle

Jackson ile How to Write a Custom Serializer hakkında bazılarına yardımcı olabilecek bir gönderi yazdım .
Sam Berry

Yanıtlar:


51

Belirtildiği gibi @JsonValue iyi bir yoldur. Ancak özel bir serileştiriciye aldırış etmiyorsanız, Öğe için bir tane yazmaya gerek yoktur, bunun yerine Kullanıcı için bir tane yazmaya gerek yoktur - öyleyse, bu kadar basit olacaktır:

public void serialize(Item value, JsonGenerator jgen,
    SerializerProvider provider) throws IOException,
    JsonProcessingException {
  jgen.writeNumber(id);
}

Yine bir başka olasılık da uygulamaktır JsonSerializable, bu durumda herhangi bir kayda gerek yoktur.

Hata olarak; bu garip - muhtemelen daha sonraki bir sürüme yükseltmek istiyorsunuz. Ancak, org.codehaus.jackson.map.ser.SerializerBasegerekli olmayan yöntemlerin standart uygulamalarına sahip olacağından (yani gerçek serileştirme çağrısı hariç her şey) genişletilmesi daha güvenlidir .


Bununla aynı hatayı alıyorum:Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.JsonTest$UserSerilizer does not define valid handledType() (use alternative registration method?) at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62) at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54) at com.example.JsonTest.<init>(JsonTest.java:27) at com.exampple.JsonTest.main(JsonTest.java:102)
Jonas

Jacskson'ın en son kararlı sürümü olan 1.8.5'i kullanıyorum.
Jonas

4
Teşekkürler. Bir bakacağım ... Ah! Aslında basittir (hata mesajı iyi olmasa da) - sadece serileştiriciyi farklı bir yöntemle kaydetmeniz gerekir, serileştiricinin hangi sınıfta olduğunu belirtmeniz gerekir: değilse, handledType () sınıfını döndürmelidir. Bu nedenle JavaType veya Class'ı bağımsız değişken olarak alan 'addSerializer'ı kullanın ve çalışmalıdır.
StaxMan

Ya bu çalıştırılmazsa?
Matej J

62

@JsonSerialize(using = CustomDateSerializer.class)Serileştirilecek nesnenin herhangi bir tarih alanını üzerine koyabilirsiniz .

public class CustomDateSerializer extends SerializerBase<Date> {

    public CustomDateSerializer() {
        super(Date.class, true);
    }

    @Override
    public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
        throws IOException, JsonProcessingException {
        SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)");
        String format = formatter.format(value);
        jgen.writeString(format);
    }

}

1
@JsonSerialize(contentUsing= ...)@JsonSerialize(contentUsing= CustomDateSerializer.class) List<Date> dates
kayda

33

Bunu da yapmayı denedim ve Jackson web sayfasındaki örnek kodda türü dahil edemeyen bir hata var (.class ) addSerializer()yöntemini çağırma yöntemine bu şöyle okunmalıdır:

simpleModule.addSerializer(Item.class, new ItemSerializer());

Başka bir deyişle, bunlar, simpleModule , serileştiriciyi ve ekleyen (önceki yanlış satır yorumlanmış olarak):

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule", 
                                          new Version(1,0,0,null));
// simpleModule.addSerializer(new ItemSerializer());
simpleModule.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(simpleModule);

Bilginize: İşte doğru örnek kod için referans: http://wiki.fasterxml.com/JacksonFeatureModules


9

@JsonValue kullanın:

public class User {
    int id;
    String name;

    @JsonValue
    public int getId() {
        return id;
    }
}

@JsonValue yalnızca yöntemlerde çalışır, bu nedenle getId yöntemini eklemeniz gerekir. Özel serileştiricinizi tamamen atlayabilmelisiniz.


2
Bunun bir Kullanıcıyı serileştirme girişimlerini etkileyeceğini ve bir Kullanıcının adının JSON üzerinden ifşa edilmesini zorlaştıracağını düşünüyorum.
Paul M

Bu çözümü kullanamıyorum çünkü ayrıca tüm kullanıcı nesnelerini tüm alanlarla serileştirebilmem gerekiyor. Ve bu çözüm bu serileştirmeyi bozacaktır çünkü sadece id-alanı dahil edilecektir. Jackson için Gson için olduğu gibi özel bir serilizer yaratmanın bir yolu yok mu?
Jonas

1
JSON Görünümlerinin (cevabımda) neden ihtiyaçlarınızla eşleşmediği konusunda yorum yapabilir misiniz?
Paul M

@user: İyi bir çözüm olabilir, onu okuyorum ve deniyorum.
Jonas

2
Ayrıca, mülkünüz (alan veya alıcı) için belirli serileştirmeyi belirtmek için @JsonSerialize (= MySerializer.class kullanarak) kullanabileceğinizi unutmayın; bu nedenle, yalnızca üye özelliği için kullanılır ve tüm tür örnekleri için KULLANILMAMALIDIR.
StaxMan

8

Özel bir Timestamp.classserileştirme / seriyi kaldırma için bir örnek yazdım , ancak bunu ne istersen kullanabilirsin.

Nesne eşleyicisini oluştururken şuna benzer bir şey yapın:

public class JsonUtils {

    public static ObjectMapper objectMapper = null;

    static {
        objectMapper = new ObjectMapper();
        SimpleModule s = new SimpleModule();
        s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
        s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
        objectMapper.registerModule(s);
    };
}

örneğin, java eebunu şu şekilde başlatabilirsiniz:

import java.time.LocalDateTime;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {

    private final ObjectMapper objectMapper;

    public JacksonConfig() {
        objectMapper = new ObjectMapper();
        SimpleModule s = new SimpleModule();
        s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
        s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
        objectMapper.registerModule(s);
    };

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return objectMapper;
    }
}

burada serileştirici şunun gibi bir şey olmalıdır:

import java.io.IOException;
import java.sql.Timestamp;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class TimestampSerializerTypeHandler extends JsonSerializer<Timestamp> {

    @Override
    public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        String stringValue = value.toString();
        if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
            jgen.writeString(stringValue);
        } else {
            jgen.writeNull();
        }
    }

    @Override
    public Class<Timestamp> handledType() {
        return Timestamp.class;
    }
}

ve şunun gibi bir şeyin serisini kaldır:

import java.io.IOException;
import java.sql.Timestamp;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class TimestampDeserializerTypeHandler extends JsonDeserializer<Timestamp> {

    @Override
    public Timestamp deserialize(JsonParser jp, DeserializationContext ds) throws IOException, JsonProcessingException {
        SqlTimestampConverter s = new SqlTimestampConverter();
        String value = jp.getValueAsString();
        if(value != null && !value.isEmpty() && !value.equals("null"))
            return (Timestamp) s.convert(Timestamp.class, value);
        return null;
    }

    @Override
    public Class<Timestamp> handledType() {
        return Timestamp.class;
    }
}

7

Bunlar Jackson serileştirmesini anlamaya çalışırken fark ettiğim davranış kalıpları.

1) Bir nesne olduğunu ve bir Sınıf Öğrenci olduğunu varsayın. Kolaylık olması için her şeyi halka açık ve nihai hale getirdim.

public class Classroom {
    public final double double1 = 1234.5678;
    public final Double Double1 = 91011.1213;
    public final Student student1 = new Student();
}

public class Student {
    public final double double2 = 1920.2122;
    public final Double Double2 = 2324.2526;
}

2) Bunların, nesneleri JSON'a serileştirmek için kullandığımız serileştiriciler olduğunu varsayalım. WriteObjectField, nesne eşleştiricisine kaydedilmişse nesnenin kendi serileştiricisini kullanır; değilse, bunu bir POJO olarak seri hale getirir. WriteNumberField, yalnızca ilkelleri bağımsız değişken olarak kabul eder.

public class ClassroomSerializer extends StdSerializer<Classroom> {
    public ClassroomSerializer(Class<Classroom> t) {
        super(t);
    }

    @Override
    public void serialize(Classroom value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        jgen.writeObjectField("double1-Object", value.double1);
        jgen.writeNumberField("double1-Number", value.double1);
        jgen.writeObjectField("Double1-Object", value.Double1);
        jgen.writeNumberField("Double1-Number", value.Double1);
        jgen.writeObjectField("student1", value.student1);
        jgen.writeEndObject();
    }
}

public class StudentSerializer extends StdSerializer<Student> {
    public StudentSerializer(Class<Student> t) {
        super(t);
    }

    @Override
    public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        jgen.writeObjectField("double2-Object", value.double2);
        jgen.writeNumberField("double2-Number", value.double2);
        jgen.writeObjectField("Double2-Object", value.Double2);
        jgen.writeNumberField("Double2-Number", value.Double2);
        jgen.writeEndObject();
    }
}

3) SimpleModule'da yalnızca DecimalFormat çıktı modeline sahip bir DoubleSerializer ###,##0.000kaydedin ve çıktı:

{
  "double1" : 1234.5678,
  "Double1" : {
    "value" : "91,011.121"
  },
  "student1" : {
    "double2" : 1920.2122,
    "Double2" : {
      "value" : "2,324.253"
    }
  }
}

POJO serileştirmesinin, Double ve Double arasında ayrım yaptığını, DoubleSerialzer'ı Çiftler için kullandığını ve çiftler için normal bir String formatı kullandığını görebilirsiniz.

4) DoubleSerializer ve ClassroomSerializer'ı StudentSerializer olmadan kaydedin. Çıktının, bir nesne olarak bir double yazarsak, bir Double gibi davranmasını ve bir Double'ı sayı olarak yazarsak, bir double gibi davranmasını bekliyoruz. Öğrenci durum değişkeni bir POJO olarak yazılmalı ve kayıt olmadığından yukarıdaki modeli takip etmelidir.

{
  "double1-Object" : {
    "value" : "1,234.568"
  },
  "double1-Number" : 1234.5678,
  "Double1-Object" : {
    "value" : "91,011.121"
  },
  "Double1-Number" : 91011.1213,
  "student1" : {
    "double2" : 1920.2122,
    "Double2" : {
      "value" : "2,324.253"
    }
  }
}

5) Tüm serileştiricileri kaydedin. Çıktı:

{
  "double1-Object" : {
    "value" : "1,234.568"
  },
  "double1-Number" : 1234.5678,
  "Double1-Object" : {
    "value" : "91,011.121"
  },
  "Double1-Number" : 91011.1213,
  "student1" : {
    "double2-Object" : {
      "value" : "1,920.212"
    },
    "double2-Number" : 1920.2122,
    "Double2-Object" : {
      "value" : "2,324.253"
    },
    "Double2-Number" : 2324.2526
  }
}

tam olarak beklendiği gibi.

Bir başka önemli not: Aynı Modüle kayıtlı aynı sınıf için birden fazla serileştiriciniz varsa, Modül o sınıf için listeye en son eklenen serileştiriciyi seçecektir. Bu kullanılmamalı - kafa karıştırıcı ve bunun ne kadar tutarlı olduğundan emin değilim

Ahlaki: Nesnenizdeki ilkellerin serileştirilmesini özelleştirmek istiyorsanız, nesne için kendi serileştiricinizi yazmalısınız. POJO Jackson serileştirmesine güvenemezsiniz.


Örneğin, Classroom olaylarını işlemek için ClassroomSerializer'a nasıl kaydolursunuz?
Trismegistos

5

Jackson'ın JSON Görünümleri , özellikle JSON formatınızda biraz esnekliğe sahipseniz, gereksinimlerinize ulaşmanın daha basit bir yolu olabilir.

Kabul {"id":7, "itemNr":"TEST", "createdBy":{id:3}}edilebilir bir temsil ise, çok az kodla bunu başarmak çok kolay olacaktır.

Kullanıcının ad alanına bir görünümün parçası olarak açıklama eklersiniz ve serileştirme isteğinizde farklı bir görünüm belirtirsiniz (açıklamasız alanlar varsayılan olarak dahil edilir)

Örneğin: Görünümleri tanımlayın:

public class Views {
    public static class BasicView{}
    public static class CompleteUserView{}
}

Kullanıcıya Açıklama Ekleyin:

public class User {
    public final int id;

    @JsonView(Views.CompleteUserView.class)
    public final String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Ve gizlemek istediğiniz alanı içermeyen bir görünümü talep ederek serileştirin (açıklamasız alanlar varsayılan olarak serileştirilir):

objectMapper.getSerializationConfig().withView(Views.BasicView.class);

Jackson JSON Görünümlerini kullanmakta zorlanıyorum ve bu sorun için iyi bir çözüm bulamıyorum.
Jonas

Jonas - Bir örnek ekledim. Aynı nesneyi farklı şekillerde serileştirmek için görünümleri gerçekten güzel bir çözüm buldum.
Paul M

İyi bir örnek için teşekkürler. Şimdiye kadarki en iyi çözüm bu. Ama createdBybir nesne yerine değer almanın bir yolu yok mu?
Jonas

setSerializationView()kullanımdan kaldırılmış gibi göründüğü için mapper.viewWriter(JacksonViews.ItemView.class).writeValue(writer, myItem);onun yerine kullandım .
Jonas

Jsonviews kullanarak bundan şüpheliyim. Görünümleri keşfetmeden önce kullandığım hızlı ve kirli bir çözüm, ilgilendiğim mülkleri bir Haritaya kopyalamak ve ardından haritayı serileştirmekti.
Paul M

5

Benim durumumda (Spring 3.2.4 ve Jackson 2.3.1), özel serileştirici için XML yapılandırması:

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="false">
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="serializers">
                        <array>
                            <bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
                        </array>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

açıklanamayan bir şekilde bir şey tarafından varsayılana geri yazılmıştır.

Bu benim için çalıştı:

CustomObject.java

@JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {

    private Long value;

    public Long getValue() {
        return value;
    }

    public void setValue(Long value) {
        this.value = value;
    }
}

CustomObjectSerializer.java

public class CustomObjectSerializer extends JsonSerializer<CustomObject> {

    @Override
    public void serialize(CustomObject value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("y", value.getValue());
        jgen.writeEndObject();
    }

    @Override
    public Class<CustomObject> handledType() {
        return CustomObject.class;
    }
}

Çözümümde XML yapılandırmasına ( <mvc:message-converters>(...)</mvc:message-converters>) gerek yok .


1

Özel serileştiricinizdeki tek gereksiniminiz namealanını serileştirmeyi atlamaksa, geçiciUser olarak işaretleyin . Jackson serileştirmeyecek veya seriyi kaldırmayacak geçici çıkarmayacaktır alanları .

[ayrıca bakınız: Java'da neden geçici alanlar var? ]


Onu nerede işaretlerim? In User-sınıfı? Ama tüm kullanıcı nesnelerini de serileştireceğim. Örneğin, önce hepsini serileştirin items(yalnızca userIdkullanıcı nesnesine referans olarak) ve sonra hepsini serileştirin users. Bu durumda -sınıfta alanları işaretleyemem User.
Jonas

Bu yeni bilgilerin ışığında, bu yaklaşım sizin için işe yaramayacaktır. Görünüşe göre Jackson özel serileştirici için daha fazla bilgi arıyor (handledType () yönteminin geçersiz kılınması gerekiyor mu?)
Mike G

Evet, ancak handledType()bağlandığım dokümantasyondaki yöntem hakkında hiçbir handledType()şey yok ve Eclipse, hayır uygulamak için yöntemler ürettiğinde üretiliyor, bu yüzden kafam karıştı.
Jonas

Emin değilim çünkü bağladığınız wiki buna başvurmuyor, ancak sürüm 1.5.1'de bir handledType () var ve istisna, yöntemin eksik veya geçersiz olduğundan şikayet ediyor gibi görünüyor (temel sınıf, yöntemden null döndürür). jackson.codehaus.org/1.5.1/javadoc/org/codehaus/jackson/map/…
Mike G

1

HandledType yöntemini geçersiz kılmalısınız ve her şey çalışacaktır

@Override
public Class<Item> handledType()
{
  return Item.class;
}

0

Sizin durumunuzdaki sorun, ItemSerializer'ın JsonSerializer tarafından geçersiz kılınması gereken handledType () yönteminin eksik olmasıdır.

    public class ItemSerializer extends JsonSerializer<Item> {

    @Override
    public void serialize(Item value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("id", value.id);
        jgen.writeNumberField("itemNr", value.itemNr);
        jgen.writeNumberField("createdBy", value.user.id);
        jgen.writeEndObject();
    }

   @Override
   public Class<Item> handledType()
   {
    return Item.class;
   }
}

Dolayısıyla handledType () ' ın tanımlanmadığı açık bir hata alıyorsunuz

Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() 

Umarım birine yardımcı olur. Cevabımı okuduğunuz için teşekkürler.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.