티스토리 뷰

Spring Frameworks

Spring boot - Object Mapper

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

1. Overview

요즘은 API 서비스가 아니더라도 VIEW단과 백엔드가 통신을 할 때 JSON을 많이 이용합니다. 객체를 json으로 직렬화하거나 json 데이터를 java 객체에 역직렬화할 때 ObjectMapper는 매우 유용합니다. 

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-starter-web을 디펜던시에 추가되었다면 별도 설정은 필요하지 않습니다. 

3. 예제

Sample Object : User.java

@Getter
@Setter
@ToString
public class User {

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

}

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

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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
글 보관함