티스토리 뷰

Spring Frameworks

Spring boot - Object Mapper

까오기 까오기 2019. 3. 4. 10:43

1. Overview

요즘은 API 서비스가 아니더라도 VIEW단과 백엔드가 통신을 할 때 JSON을 많이 이용합니다. 그렇게 함으로써 복잡한 구조의 데이터를 쉽게 서로 주고 받을 수 있습니다. 그렇다보니 java 객체를 json으로 직렬화 하거나 json 데이터를 java 객체에 역직렬화할 일이 많습니다. 스프링 프레임워크에서는 @RestController를 사용함으로써 자동으로 수비게 결과 값을 반환할 수 있습니다. 그러나 전달 받은 값을 처리할 때는 서비스에 따라 커스터마이징을 해줘야 할 수도 있습니다.  json 데이터를 java 객체에 매핑하는 여러 방법이 있지만 그중에 Jackson 라이브러리가 비교적 쉽게 쓸 수 있어 소개합니다. 

2. Dependencies 추가

1
2
3
4
5
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
cs


spring-boot-steter-web을 디펜던시에 추가하면 끝 ~~~  


Complie Dependencies에 spring-boot-starter-json이 자동으로 추가되는 것을 볼 수 있습니다.


spring-boot-starter-json은 jackson 라이브러리를 디펜던시로 추가합니다. 


jackson-databind에는 jackson-annotations와 jackson-core가 디펜던시로 잡혀 있습니다. 


아무튼 설정은 끝~~~ 

3. 예제

Sample Object : User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class User implements Serializable{
 
    private static final long serialVersionUID = 1L;
 
    private String userId;
    private String userPwd;
    private String name;
    private String authType;
    private Date timestamp;
    private boolean isUpdate = false;
    
    public User() {
        super();
    }
    
    public User(String userId, String name, String authType) {
        super();
        this.userId = userId;
        this.name = name;
        this.authType = authType;
    }
 
    public String getUserId() {
        return userId;
    }
 
    public void setUserId(String userId) {
        this.userId = userId;
    }
 
    public String getUserPwd() {
        return userPwd;
    }
 
    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getAuthType() {
        return authType;
    }
 
    public void setAuthType(String authType) {
        this.authType = authType;
    }
 
    public Date getTimestamp() {
        return timestamp;
    }
 
    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }
 
    public boolean getIsUpdate() {
        return isUpdate;
    }
 
    public void setIsUpdate(boolean isUpdate) {
        this.isUpdate = isUpdate;
    }
 
    @Override
    public String toString() {
        return "User [userId=" + userId + ", userPwd=" + userPwd + ", name=" + name + ", authType=" + authType
                + ", timestamp=" + timestamp + ", isUpdate=" + isUpdate + "]";
    }
    
}
cs

3-1. java Object to JSON 

Java 객체를 읽어서 json String으로 직렬화 하기 
1
2
3
4
5
6
7
8
9
    final String expected = "{\"userId\":\"userId\",\"userPwd\":null,\"name\":\"tester\",\"authType\":\"web\",\"timestamp\":null,\"isUpdate\":false}";
 
    @Test
    public void objectToJson() throws JsonProcessingException {
        User user = new User("userId""tester""web");
        ObjectMapper objectMapper = new ObjectMapper();
        String userAsString = objectMapper.writeValueAsString(user);
        assertThat(userAsString, equalTo(expected));
    }
cs

3-2. JSON to Java Object 

Json String을 읽어서 Java 객체에 역직렬화하기 

1
2
3
4
5
6
7
8
    final String expected = "{\"userId\":\"userId\",\"userPwd\":null,\"name\":\"tester\",\"authType\":\"web\",\"timestamp\":null,\"isUpdate\":false}";
 
    @Test
    public void jsonToObject() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        User user = objectMapper.readValue(expected, User.class);
        assertThat(user.getUserId(), equalTo("userId"));
    }
cs

3-3. JSON 배열을 Java List에 매핑하기 

1
2
3
4
5
6
7
8
9
10
11
12
13
    final String expected1 = "{\"userId\":\"userId1\",\"userPwd\":null,\"name\":\"tester1\",\"authType\":\"web\",\"timestamp\":null,\"isUpdate\":false}";
    final String expected2 = "{\"userId\":\"userId2\",\"userPwd\":null,\"name\":\"tester2\",\"authType\":\"web\",\"timestamp\":null,\"isUpdate\":false}";
    final String listExpected = "["+expected1+","+expected2+"]";
 
    @Test
    public void jsonToListObject() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        List<User> users = objectMapper.readValue(listExpected, new TypeReference<List<User>>(){});
        for(User user : users) {
            System.out.println(user.toString());
            assertThat(user.getName(), containsString("tester"));
        }
    }
cs

3-4. JSON 데이터를 Map에 매핑하기 

1
2
3
4
5
6
7
8
    final String expected1 = "{\"userId\":\"userId1\",\"userPwd\":null,\"name\":\"tester1\",\"authType\":\"web\",\"timestamp\":null,\"isUpdate\":false}";
 
    @Test
    public void jsonToMap() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> map = objectMapper.readValue(expected1, new TypeReference<Map<String,Object>>(){});
        assertThat(map.get("userId"), equalTo("userId1"));
    }
cs

4. 몇가지 고급 기능 

4-1. 존재하지 않는 필드 무시 설정 

기존에 JSON String에 새로운 필드를 추가하고 역직렬화를 실행하면 UnrecognizedPropertyException이 발생합니다. 

실무에서 무수히 많은 데이터를 포함한 JSON 데이터에서 필요로 하는 것만 매핑할 때 많이 사용하는 옵션입니다. 

간단하게 아래 한 줄만 추가해 주면 됩니다. 

1
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 없는 필드로 인한 오류 무시
cs


테스트 

1
2
3
4
5
6
7
8
9
10
    final String expected1 = "{\"userId\":\"userId1\",\"userPwd\":null,\"name\":\"tester1\",\"authType\":\"web\",\"timestamp\":null,\"isUpdate\":false,\"authGroup\":\"admin\" }";
 
    @Test
    public void objectToJson() throws JsonProcessingException {
        User user = new User("userId1""tester1""web");
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 없는 필드로 인한 오류 무시
        String userAsString = objectMapper.writeValueAsString(user);
        assertThat(userAsString, containsString("userId1"));
    }
cs

4-2. 사용자 정의 Serializer, Deserializer 사용하기 

사용자 정의 직렬화 예제 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CustomUserSerializer extends StdSerializer<User>{
 
    private static final long serialVersionUID = 1L;
 
    public CustomUserSerializer() {
        this(null);
    }
 
    public CustomUserSerializer(Class<User> user) {
        super(user);
    }
 
    @Override
    public void serialize(
            User user, JsonGenerator jsonGenerator, SerializerProvider serializer) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("userId", user.getUserId());
        jsonGenerator.writeStringField("userName", user.getName());
        jsonGenerator.writeStringField("authType", user.getAuthType());
        //jsonGenerator.writeNumberField("timestamp", (new Date()).getTime());
        jsonGenerator.writeEndObject();
    }
}
cs


테스트 

1
2
3
4
5
6
7
8
9
10
11
12
    @Test
    public void customObjectToJson() throws JsonProcessingException {
        User user = new User("userId1""tester1""web");
 
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(User.classnew CustomUserSerializer());
        objectMapper.registerModule(simpleModule);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 없는 필드로 인한 오류 무시
        String userAsString = objectMapper.writeValueAsString(user);
        assertThat(userAsString, equalTo("{\"userId\":\"userId1\",\"userName\":\"tester1\",\"authType\":\"web\"}"));
    }
cs


사용자 정의 역직렬화 예제 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CustomUserDeserializer extends StdDeserializer<User>{
 
    private static final long serialVersionUID = 1L;
 
    public CustomUserDeserializer() {
        this(null);
    }
 
    public CustomUserDeserializer(Class<?> user) {
        super(user);
    }
 
    @Override
    public User deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        User user = new User();
        ObjectCodec codec = jsonParser.getCodec();
        JsonNode node = codec.readTree(jsonParser);
        JsonNode userNameNode = node.get("name");
        JsonNode userIdNode = node.get("userId");
        JsonNode authTypeNode = node.get("authType");
        String userName = userNameNode.asText();
        String userid = userIdNode.asText();
        String authType = authTypeNode.asText();
        user.setUserId(userid);
        user.setName(userName);
        user.setAuthType(authType);
        return user;
    }
 
}
cs


테스트 

1
2
3
4
5
6
7
8
9
10
11
12
    final String expected1 = "{\"userId\":\"userId1\",\"userPwd\":null,\"name\":\"tester1\",\"authType\":\"web\",\"timestamp\":null,\"isUpdate\":false,\"authGroup\":\"admin\" }";
 
    @Test
    public void customJsonToObject() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addDeserializer(User.classnew CustomUserDeserializer());
        objectMapper.registerModule(simpleModule);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 없는 필드로 인한 오류 무시
        User user = objectMapper.readValue(expected1, User.class);
        assertThat(user.getUserId(), equalTo("userId1"));
    }
cs

4.3 사용자정의 ObjectMapper 활용 

프로젝트 초기 네이밍 규칙 및 컨벤션에 따라 몇가지를 정의합니다. 

예를 들어 화폐 등에서 세자리 마다 콤마를 사용하고 있다면 Integer, Long으로 정의된 프라퍼티에서 콤마는 제거하도록 deserializer를 만들어서 등록합니다. 날짜 포맷의 문자열인 경우 Date는 "yyyy-MM-dd"로 하고 Timestamp의 경우 "yyyy-MM-dd HH:mm:ss"로 처리한다고 정의를 합니다. 그에 따라 deserializer를 만들어서 CustomObjectMapper의 생성자에 만들어서 등록을 하고 모든 개발자는 이 매퍼를 사용하라고 가이드를 합니다. 


User.java

날짜 형식에 따른 처리를 위해 Date type, Timestamp type 두개를 만듭니다. 

1
2
3
4
5
6
7
8
9
10
11
12
public class User implements Serializable{
 
    private String userId;
    private String userPwd;
    private String name;
    private String authType;
    private Timestamp timestamp;
    private Date date;
    private boolean isUpdate = false;
    ...
    ...
}
cs

CustomDateDeserializer

Date deserializer 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CustomDateDeserializer extends JsonDeserializer<Date>{
 
    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
 
    public CustomDateDeserializer() {
        super();
    }
 
    @Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String valueAsString = jsonParser.getValueAsString();
        if (StringUtils.isEmpty(valueAsString)) {
            return null;
        }
        try {
            SimpleDateFormat transFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
            //transFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            return transFormat.parse(valueAsString);
        }catch(Exception e) {
            return null;
        }
    }
 
}
cs

CustomTimestampDeserializer

Timestamp deserializer 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CustomTimestampDeserializer extends JsonDeserializer<Timestamp>{
 
    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
 
    public CustomTimestampDeserializer() {
        super();
    }
 
    @Override
    public Timestamp deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String valueAsString = jsonParser.getValueAsString();
        if (StringUtils.isEmpty(valueAsString)) {
            return null;
        }
        try {
            SimpleDateFormat transFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
            //transFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            return new Timestamp(transFormat.parse(valueAsString).getTime());
        }catch(Exception e) {
            return null;
        }
    }
 
}
cs

CustomObjectMapper

생성자에 프로젝트에서 사용하는 Serializer, Deserializer 등을 등록합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CustomObjectMapper extends ObjectMapper {
    private static final long serialVersionUID = 1L;
 
    public CustomObjectMapper(){
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addDeserializer(Timestamp.classnew CustomTimestampDeserializer());
        simpleModule.addDeserializer(Date.classnew CustomDateDeserializer());
//        simpleModule.addDeserializer(Long.class, new LongDeserializer());
//        simpleModule.addDeserializer(Integer.class, new IntegerDeserializer());
//        simpleModule.addDeserializer(Float.class, new FloatDeserializer());
//        simpleModule.addDeserializer(Double.class, new DoubleDeserializer());
        registerModule(simpleModule);
        configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 없는 필드로 인한 오류 무시
    }
}
cs


테스트 

1
2
3
4
5
6
7
8
    final String expected3 = "{\"userId\":\"userId3\",\"userPwd\":null,\"name\":\"tester2\",\"authType\":\"web\",\"timestamp\":\"2019-03-04 13:59:59\",\"date\":\"2019-03-04\",\"isUpdate\":false,\"authGroup\":\"user\" }";
 
    @Test
    public void customObjectMapper() throws IOException {
        ObjectMapper objectMapper = new CustomObjectMapper();
        User user = objectMapper.readValue(expected3, User.class);
        assertThat(user.getTimestamp().toLocalDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), equalTo("2019-03-04 13:59:59"));
    }
cs

참고 

Intro to the Jackson ObjectMapper

https://www.baeldung.com/jackson-object-mapper-tutorial


Github : https://github.com/kkaok/examples


댓글
댓글쓰기 폼