简体   繁体   中英

Jackson Custom Deserialize

I would like deserialize my custom serialized objects. My objects are basically consisting a simple Pair implementation.

class School{
  Integer id;
  String schoolName;
}

class Student{
  Integer id;
  Integer schoolId;
  String studentName;
}

@JsonSerialize(using=PairSerializer.class)
public class Pair<V,K>{
   V v;
   K k;
}

Here is the result

 [
   {
      "v":{
         "id":1,
         "schoolId":3,
         "studentName":"O. Bas"
      },
      "k":{
         "id":3,
         "schoolName":"School 3"
      }
   },
   {
      "v":{
         "id":2,
         "schoolId":3,
         "studentName":"C. Koc"
      },
      "k":{
         "id":3,
         "schoolName":"School 3"
      }
   }
]

v and k as field name in json is pretty ugly. That is why I have written a custom serializer as this:

@Override
public void serialize(Pair pair, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
  jsonGenerator.writeStartObject();
  jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getK().getClass().getSimpleName() ), pair.getK());
  jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getV().getClass().getSimpleName() ), pair.getV());
  jsonGenerator.writeEndObject();
}

The result is exactly what I want. v and k field names are replaced by their class names.

[
   {
      "school":{
         "id":3,
         "schoolName":"School 3"
      },
      "student":{
         "id":1,
         "schoolId":3,
         "studentName":"O. Bas"
      }
   },
   {
      "school":{
         "id":3,
         "schoolName":"School 3"
      },
      "student":{
         "id":2,
         "schoolId":3,
         "studentName":"C. Koc"
      }
   }
]

Here is the my question. How can I deserialize my json string to List<Pair<V, K> ? The real problem is that V and K are depends on the deserialized context it might vary as Student, School or another pair implementation.

public class PairDeserializer extends JsonDeserializer<Pair> {

   public PairDeserializer() {
   }

   @Override
   public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
         // I need to Deserialized generic type information of Pair

   }
}

I think, you should create your own PropertyNamingStrategy . For example see my simple implementation:

class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {

    private static final long serialVersionUID = 1L;

    private Map<String, String> mapping;

    public MapTransformNamingStrategy(Map<String, String> mapping) {
        this.mapping = mapping;
    }

    @Override
    public String translate(String property) {
        if (mapping.containsKey(property)) {
            return mapping.get(property);
        }

        return property;
    }
}

Now you can use it in this way:

Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
//etc

Example JSON output:

{ "school" : { "id" : 1,
      "schoolName" : "The Best School in the world"
    },
  "student" : { "id" : 1,
      "schoolId" : 1,
      "studentName" : "Arnold Shwarz"
    }
}

EDIT

Because my answer is not clear for everyone I present full example source code which serialize Java POJO objects into JSON and "vice versa".

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy;

public class JacksonProgram {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws Exception {
        List<Pair<Student, School>> pairs = createDataForSerialization();
        Map<String, String> mapping = createSchoolStudentMapping();

        JsonConverter jsonConverter = new JsonConverter(mapping);

        String json = jsonConverter.toJson(pairs);
        System.out.println("JSON which represents list of pairs:");
        System.out.println(json);

        List<Pair<Student, School>> value = jsonConverter.fromJson(json, List.class);
        System.out.println("----");
        System.out.println("Deserialized version:");
        System.out.println(value);
    }

    private static Map<String, String> createSchoolStudentMapping() {
        Map<String, String> mapping = new HashMap<String, String>();
        mapping.put("k", "student");
        mapping.put("v", "school");

        return mapping;
    }

    private static List<Pair<Student, School>> createDataForSerialization() {
        List<Pair<Student, School>> pairs = new ArrayList<Pair<Student, School>>();
        pairs.add(new Pair<Student, School>(new Student(1, 3, "O. Bas"), new School(3, "School 3")));
        pairs.add(new Pair<Student, School>(new Student(2, 4, "C. Koc"), new School(4, "School 4")));

        return pairs;
    }
}

class JsonConverter {

    private Map<String, String> mapping;
    private ObjectMapper objectMapper;
    private JsonFactory jsonFactory;

    public JsonConverter(Map<String, String> mapping) {
        this.mapping = mapping;
        initJsonObjects();
    }

    private void initJsonObjects() {
        objectMapper = new ObjectMapper();
        objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));

        jsonFactory = new JsonFactory();
    }

    public String toJson(Object object) throws Exception {
        StringWriter stringWriter = new StringWriter();
        JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter);
        objectMapper.writeValue(jsonGenerator, object);

        return stringWriter.toString();
    }

    public <T> T fromJson(String json, Class<T> expectedType) throws Exception {
        JsonParser jsonParser = jsonFactory.createJsonParser(json);

        return objectMapper.readValue(jsonParser, expectedType);
    }
}

class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {

    private static final long serialVersionUID = 1L;

    private Map<String, String> mapping;

    public MapTransformNamingStrategy(Map<String, String> mapping) {
        this.mapping = mapping;
    }

    @Override
    public String translate(String property) {
        if (mapping.containsKey(property)) {
            return mapping.get(property);
        }

        return property;
    }
}

class School {

    private Integer id;
    private String schoolName;

    public School() {
    }

    public School(Integer id, String schoolName) {
        this.id = id;
        this.schoolName = schoolName;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    @Override
    public String toString() {
        return "School [id=" + id + ", schoolName=" + schoolName + "]";
    }

}

class Student {

    private Integer id;
    private Integer schoolId;
    private String studentName;

    public Student() {
    }

    public Student(Integer id, Integer schoolId, String studentName) {
        this.id = id;
        this.schoolId = schoolId;
        this.studentName = studentName;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getSchoolId() {
        return schoolId;
    }

    public void setSchoolId(Integer schoolId) {
        this.schoolId = schoolId;
    }

    public String getStudentName() {
        return studentName;
    }

    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + ", schoolId=" + schoolId + ", studentName=" + studentName
                + "]";
    }

}

class Pair<V, K> {

    private V v;
    private K k;

    public Pair() {
    }

    public Pair(V v, K k) {
        this.v = v;
        this.k = k;
    }

    public V getV() {
        return v;
    }

    public void setV(V v) {
        this.v = v;
    }

    public K getK() {
        return k;
    }

    public void setK(K k) {
        this.k = k;
    }

    @Override
    public String toString() {
        return "Pair [v=" + v + ", k=" + k + "]";
    }
}

The full output log:

JSON which represents list of pairs:
[{"school":{"id":1,"schoolId":3,"studentName":"O. Bas"},"student":{"id":3,"schoolName":"School 3"}},{"school":{"id":2,"schoolId":4,"studentName":"C. Koc"},"student":{"id":4,"schoolName":"School 4"}}]
----
Deserialized version:
[{school={id=1, schoolId=3, studentName=O. Bas}, student={id=3, schoolName=School 3}}, {school={id=2, schoolId=4, studentName=C. Koc}, student={id=4, schoolName=School 4}}]

Because the output JSON is not formatted I present it in more understandable version:

[
   {
      "school":{
         "id":1,
         "schoolId":3,
         "studentName":"O. Bas"
      },
      "student":{
         "id":3,
         "schoolName":"School 3"
      }
   },
   {
      "school":{
         "id":2,
         "schoolId":4,
         "studentName":"C. Koc"
      },
      "student":{
         "id":4,
         "schoolName":"School 4"
      }
   }
]

As you can see, we create new JsonConverter object with definition of mapping between Pair property names and which names we want to see in JSON string representation. Now if you have for example Pair<School, Room> you can create mapping Map in this way:

private static Map<String, String> createSchoolRoomMapping() {
    Map<String, String> mapping = new HashMap<String, String>();
    mapping.put("k", "school");
    mapping.put("v", "room");

    return mapping;
}

I was going for an answer with some annotation ( JsonTypeInfo and JsonUnwrapped ), but those two don't work well together apparently (see this issue ). That would of handled both the serialization and deserialization part of your problem, without relying on custom de/serializer. Instead, you'll need a custom deserializer, which does something along those line:

class PairDeserializer extends JsonDeserializer<Pair>{
    static Map<String, Class> MAPPINGS = new HashMap<String, Class>();
    @Override
    public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        Object key = deserializeField(jp);
        Object value = deserializeField(jp);
        Pair pair = new Pair();
        pair.k = key;
        pair.v = value;
        jp.nextToken();
        return pair;
    }

    private Object deserializeField(JsonParser jp) throws IOException, JsonParseException, JsonProcessingException {
        jp.nextValue();
        String className = jp.getCurrentName();
        return jp.readValueAs(MAPPINGS.get(className));
    }
}

Then you only need to register the mappings you need

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM