0

I'm writing rest services using jersey and jackson. I have something like this example

import com.mkyong.Track;

@Path("/json/metallica")
public class JSONService {

   @GET
   @Path("/get")
   @Produces(MediaType.APPLICATION_JSON)
   public Track getTrackInJSON() {

       Track track = new Track();
       track.setTitle("Enter Sandman");
       track.setSinger("Metallica");
       return track;
   }

   @POST
   @Path("/post")
   @Consumes(MediaType.APPLICATION_JSON)
   public MyResponse createTrackInJSON(Track track) {

       return new MyResponse().setResult(true);
   }
}

But in my case, the classe Track is not a simple pojo bean.I use a Map to save my data and I create a method to generate json from my object and a constructor to parse json data.

public class JsonObject {

    Map<String, String> data = new HashMap<>();

    public String toJson(){
        return "";
    }
}
public class Track extends JsonObject {

    public Track(String json) {
        //Parse json
        // [...]
    }

    public Track(JsonNode node) {
        //Parse node
        // [...]
    }


    public String getTitle() {
        if(data.containsKey("title"))
            return data.get("title");
        return "";
    }

    public void setTitle(String title) {
        data.put("title", title);
    }

    public String getSinger() {
        if(data.containsKey("singer"))
            return data.get("singer");
        return "";
    }

    public void setSinger(String singer) {
        data.put("singer", singer);
    }

    @Override
    public String toString() {
        return "Track [title=" + getTitle() + ", singer=" + getSinger() + "]";
    }

    public String toJson() {
        return "{\"title\": \"" + getTitle() + "\", \"singer\": \"" + getSinger() + "\"}";
    }
}

public class MyResponse extends JsonObject {

    public boolean getResult() {
        if(data.containsKey("result"))
            return (boolean) data.get("result");
        return false;
    }

    public MyResponse setResult(boolean value) {
        data.put("result", value);
        return this;
    }

    @Override
    public String toString() {
        return "{\"result\": " + getResult() + "}";
    }
}

So my question is: is it possible to create actions before and after the method call to tell to jackson how to generate or parse my object ? using annotation and/or creating an ObjectReader or something like that ?

Thanks


Edit :

Thanks peeskillet.

I'm not sure @JsonAnyGetter et @JsonAnySetter are my solution. I have many objects that extend JsonObject and I want to keep it with getters and setters for my rest api.

So I created a generic JsonSerializer:

public class MyObjectSerializer extends JsonSerializer<JsonObject> {

    @Override
    public void serialize(JsonObject value, JsonGenerator gen, SerializerProvider serializers)
        throws IOException, JsonProcessingException {

        gen.writeRaw(value.toJson());
    }
}

Then I add this annotation to MyResponse object.

@JsonSerialize(using = MyObjectSerializer.class)
public class MyResponse ...

I wish I did not have to add this annotation in each objects and that was done automatically during rest service return but it works fine and it's not so restrictive.

Now I have another problem with deserialization. I want a generic deserializer calling constructor with parameter JsonNode. But how do I know what class call?

I saw a parameter "as" in @JsonDeserialize annotation.

@JsonDeserialize(using = MyObjectDeserializer.class, as=Track.class)

But I don't find how get this information in the JsonDeserializer. Any idea ?

(Maybe I could open another thread for this question)

AlexB
  • 51
  • 10

2 Answers2

2

I solved my problem.

For the serialization, I created a serializer for my JsonObject I defined in a contextResolver. All classes that extend JsonObject are serialized using this serializer.

    public class MyJsonSerializer extends JsonSerializer<JsonObject> {

        @Override
        public void serialize(JsonObject value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException, JsonProcessingException {

            gen.writeRaw(value.toJson());
        }
    }


    @Provider
    public class JacksonJsonProvider implements ContextResolver<ObjectMapper> {

        private static final ObjectMapper MAPPER = new ObjectMapper();

        static {
            MAPPER.setSerializationInclusion(Include.NON_NULL);
            MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
            MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        }

        public JacksonJsonProvider() {

            SimpleModule simpleModule = new SimpleModule("SimpleModule", new Version(1,0,0,null, null, null));
            simpleModule.addSerializer(JsonObject.class, new MyJsonSerializer());
            MAPPER.registerModule(simpleModule);

        }

        @Override
        public ObjectMapper getContext(Class<?> type) {
            LOGGER.debug("JacksonProvider.getContext() called with type: "+type);
            return MAPPER;
        }
    }

For deserialization, I use the annotation @JsonCreator() to indicate to jackson what method used to create object.

    @JsonCreator()
    public JsonObject(JsonNode json) {
        super(json);
    }
AlexB
  • 51
  • 10
0

To configure Jackson in JAX-RS, you can register a Context-Resolver<ObjectMapper>, as seen in this post. You can create custom serializers if you need to.

For your specific use case posted above, something as simple at using @JsonAnyGetter would work without doing any other crazy stuff. @JsonAnySetter for deserialization.

public class JsonObject {

    Map<String, String> data = new HashMap<>();

    @JsonAnyGetter
    public Map<String, String> getData() {
        return data;
    }

    @JsonAnySetter
    public void put(String field, String value) {
        data.put(field, value);
    }

    public String toJson(){
        return "";
    }
}

Jackson will serialize all the data in the map, as if they were properties in the class or subclasses. So you don't need to add any properties in the Track class.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720