如何在Jackson上使用自定义序列化程序?


111

我有两个要使用Jackson序列化为JSON的Java类:

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

我想将Item序列化为此JSON:

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

用户序列化为仅包含id。我还将能够将所有用户对象序列化为JSON,例如:

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

所以我想我需要为此编写一个自定义的序列化程序Item并尝试过:

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

}

我使用来自Jackson How-to:Custom Serializers的代码对JSON进行了序列化

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

但是我得到这个错误:

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上使用自定义序列化程序?


这就是我对Gson的处理方式:

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

但是我现在需要和Jackson一起做,因为Gson不支持接口。


您如何/在何处让Jackson使用您的自定义序列化程序Item?我的控制器方法返回标准的序列化对象时遇到问题TypeA,但是对于另一个特定的控制器方法,我想以不同的方式对其进行序列化。那会是什么样?
Don Cheadle 2015年

我写了一篇有关如何用Jackson编写自定义序列化程序的文章,这可能对某些人有所帮助。
山姆·贝里

Answers:


51

如前所述,@ JsonValue是一个好方法。但是,如果您不介意自定义序列化程序,则无需为Item编写一个,而为User编写一个。如果是这样,它将很简单:

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

还有另一种可能性是实施JsonSerializable,在这种情况下,不需要注册。

关于错误;这很奇怪-您可能想升级到更高版本。但是,org.codehaus.jackson.map.ser.SerializerBase由于它具有非必要方法的标准实现(即,除了实际的序列化调用之外的所有方法),因此进行扩展也更安全。


这样,我会得到相同的错误: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的最新稳定版本1.8.5。
乔纳斯(Jonas)

4
谢谢。我看看...啊!它实际上很简单(尽管错误消息不是很好)-您只需要使用其他方法注册序列化器,以指定序列化器用于的类:如果没有,则必须从handledType()返回类。因此,使用以JavaType或Class作为参数的“ addSerializer”,它应该可以工作。
StaxMan 2011年

如果不运行该怎么办?
Matej J

62

您可以放置@JsonSerialize(using = CustomDateSerializer.class)要序列化的对象的任何日期字段。

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
coderatchet

33

我也尝试过这样做,并且在Jackson页面上的示例代码中有一个错误,该错误未能.class在对addSerializer()方法的调用中包含类型(),该内容应如下所示:

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

换句话说,这些行实例化simpleModule并添加了序列化程序(注释掉了之前的错误行):

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

仅供参考:这是正确的示例代码的参考:http : //wiki.fasterxml.com/JacksonFeatureModules


9

使用@JsonValue:

public class User {
    int id;
    String name;

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

@JsonValue仅适用于方法,因此您必须添加getId方法。您应该可以完全跳过自定义序列化程序。


2
我认为这会影响序列化User的所有尝试,因此很难通过JSON公开User的名称。
Paul M

我不能使用此解决方案,因为我还需要能够序列化具有所有字段的所有用户对象。由于仅包含id字段,因此该解决方案将破坏该序列化。像为Gson一样,没有办法为Jackson创建定制的Serilizer吗?
乔纳斯(Jonas)

1
您能否评论为什么JSON视图(在我的回答中)不符合您的需求?
Paul M

@user:这可能是一个很好的解决方案,我正在阅读并尝试。
乔纳斯(Jonas)

2
还要注意,您可以使用@JsonSerialize(using = MySerializer.class)来指示属性(字段或getter)的特定序列化,因此它仅用于成员属性,而不用于所有类型的实例。
StaxMan

8

我为自定义Timestamp.class序列化/反序列化编写了一个示例,但是您可以将其用于任何所需的操作。

创建对象映射器时,请执行以下操作:

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

例如,java ee您可以使用以下代码初始化它:

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

序列化器应该是这样的:

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

和反序列化器是这样的:

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

这些是我在尝试了解Jackson序列化时注意到的行为模式。

1)假设有一个对象Classroom和class Student。为了方便起见,我已将所有内容公开化并进行了最终化。

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)假设这些是我们用于将对象序列化为JSON的序列化器。如果对象已向对象映射器注册,则writeObjectField将使用对象自己的序列化程序;如果不是,则将其序列化为POJO。writeNumberField只接受基元作为参数。

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)###,##0.000在SimpleModule中仅注册具有DecimalFormat输出模式的DoubleSerializer,并且输出为:

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

您可以看到,将DoubleSerialzer用于Doubles,将常规String格式用于double,POJO序列化可区分double和Double。

4)注册DoubleSerializer和ClassroomSerializer,但不使用StudentSerializer。我们期望输出是这样的:如果我们将double编写为对象,则其行为类似于Double,如果我们将Double编写为数字,则其行为类似于double。Student实例变量应写为POJO并遵循上述模式,因为它没有注册。

{
  "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)注册所有序列化器。输出为:

{
  "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
  }
}

完全符合预期。

另一个重要说明:如果在同一模块中注册了针对同一类的多个序列化器,则模块将为该类选择最近添加到列表中的序列化器。这不应该使用-这很混乱,我不确定这是多么一致

道德:如果要自定义对象中原语的序列化,则必须为对象编写自己的序列化器。您不能依赖POJO Jackson序列化。


您如何注册ClassroomSerializer来处理例如Classroom的出现?
Trismegistos

5

杰克逊(Jackson)的JSON视图(JSON Views)可能是满足要求的一种更简单的方法,尤其是如果您在JSON格式上具有一定的灵活性时。

如果{"id":7, "itemNr":"TEST", "createdBy":{id:3}}是可接受的表示形式,那么只需很少的代码就可以很容易地实现。

您只需将User的名称字段注释为视图的一部分,并在序列化请求中指定其他视图(默认情况下将包括未注释的字段)

例如:定义视图:

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

注释用户:

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

然后序列化请求一个不包含要隐藏的字段的视图(默认情况下,未注释的字段将被序列化):

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

我发现Jackson JSON Views难以使用,并且无法解决此问题。
乔纳斯(Jonas)

乔纳斯-我添加了一个例子。我发现视图是一种以不同方式序列化同一对象的非常好的解决方案。
Paul M

感谢您的好榜样。这是迄今为止最好的解决方案。但是,有没有办法获取createdBy值而不是对象呢?
乔纳斯(Jonas)

setSerializationView()似乎已弃用,所以我mapper.viewWriter(JacksonViews.ItemView.class).writeValue(writer, myItem);改用了。
乔纳斯(Jonas)

我怀疑使用jsonviews。在发现视图之前,我使用了一种快速而又肮脏的解决方案,就是将我感兴趣的属性复制到Map中,然后序列化Map。
Paul M

5

就我而言(Spring 3.2.4和Jackson 2.3.1),用于自定义序列化程序的XML配置:

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

被无法解释的方式改写回默认值。

这对我有用:

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

<mvc:message-converters>(...)</mvc:message-converters>我的解决方案中不需要XML配置()。


1

如果您在自定义序列化程序中的唯一要求是跳过序列化的name字段User,请将其标记为瞬态。Jackson不会序列化或反序列化瞬态字段。

[另请参见:Java为什么具有过渡字段?]


我在哪里标记?在User-class中?但是我也将序列化所有用户对象。例如,首先仅序列化所有items(仅userId作为对用户对象的引用),然后序列化all users。在这种情况下,我无法在User-class中标记这些字段。
乔纳斯(Jonas)

根据这些新信息,这种方法对您不起作用。杰克逊似乎正在寻找有关自定义序列化程序的更多信息(handledType()方法是否需要覆盖?)
Mike G

是的,但是handledType()我链接到的文档中没有关于该方法的任何信息,并且当Eclipse生成实现no的方法时handledType()也会生成,所以我很困惑。
乔纳斯(Jonas)

我不确定,因为您链接的Wiki没有引用它,但是在1.5.1版中有一个handleType(),并且异常似乎在抱怨该方法丢失或无效(基类从该方法返回null)。jackson.codehaus.org/1.5.1/javadoc/org/codehaus/jackson/map/…–
Mike G


0

您遇到的问题是ItemSerializer缺少需要从JsonSerializer重写的handleType()方法

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

因此,你得到了明确的错误handledType()没有定义

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

希望它能帮助某人。感谢您阅读我的回答。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.