AndroidJava프로그래밍

[java] LinkedHashMap에 대한 데이터 set, get 예제 총정리 맵 유틸 등


LinkedHashMap<String, Object> 예제

import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {
    public static void main(String[] args) {
        // LinkedHashMap 선언
        LinkedHashMap<String, Object> map = new LinkedHashMap<>();

        // 값 저장 (set)
        map.put("name", "홍길동");
        map.put("age", 30);
        map.put("isMember", true);
        map.put("height", 175.5);

        // 값 조회 (get)
        String name = (String) map.get("name");
        int age = (int) map.get("age");
        boolean isMember = (boolean) map.get("isMember");
        double height = (double) map.get("height");

        // 출력
        System.out.println("이름: " + name);
        System.out.println("나이: " + age);
        System.out.println("회원 여부: " + isMember);
        System.out.println("키: " + height);

        // 전체 순회
        System.out.println("\n== 전체 출력 ==");
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

주요 포인트

  • LinkedHashMap입력 순서 유지.
  • Object 타입이기 때문에 get할 때는 형변환 필요.
  • Map.Entry를 활용하면 전체를 순회하면서 key/value 출력 가능.

안전한 꺼내기: null 체크 + 타입 체크

Object obj = map.get("age");
if (obj instanceof Integer) {
    int age = (Integer) obj;
    System.out.println("나이: " + age);
} else {
    System.out.println("나이가 올바르게 저장되어 있지 않음");
}

null 체크만 필요한 경우

if (map.containsKey("name")) {
    String name = (String) map.get("name");
    System.out.println("이름: " + name);
} else {
    System.out.println("이름 정보 없음");
}

✅ 안전한 get 메서드 유틸리티 (예시)

실무에서는 이런 식의 유틸리티 메서드를 만들어두면 깔끔하게 처리할 수 있어요:

public class MapUtils {
    public static String getString(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value instanceof String ? (String) value : null;
    }

    public static Integer getInt(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value instanceof Integer ? (Integer) value : null;
    }

    public static Boolean getBoolean(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value instanceof Boolean ? (Boolean) value : null;
    }

    // 필요한 타입들 추가 가능
}

사용 예시:

String name = MapUtils.getString(map, "name");
Integer age = MapUtils.getInt(map, "age");

if (name != null) {
    System.out.println("이름: " + name);
}



🔐 보너스: Optional 사용 예시 (Java 8+)

Optional.ofNullable(map.get("age"))
        .filter(val -> val instanceof Integer)
        .map(val -> (Integer) val)
        .ifPresent(age -> System.out.println("나이: " + age));

LinkedHashMap<String, Object>용 안전한 유틸리티 클래스 (완성형)

import java.util.Map;

public class MapUtils {

    public static String getString(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return (value instanceof String) ? (String) value : null;
    }

    public static Integer getInt(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return (value instanceof Integer) ? (Integer) value : null;
    }

    public static Long getLong(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return (value instanceof Long) ? (Long) value : null;
    }

    public static Boolean getBoolean(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return (value instanceof Boolean) ? (Boolean) value : null;
    }

    public static Double getDouble(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return (value instanceof Double) ? (Double) value : null;
    }

    public static Object getOrDefault(Map<String, Object> map, String key, Object defaultValue) {
        return map.getOrDefault(key, defaultValue);
    }

    public static boolean containsNonNull(Map<String, Object> map, String key) {
        return map.containsKey(key) && map.get(key) != null;
    }
}

사용 예:

LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("name", "홍길동");
map.put("age", 25);

String name = MapUtils.getString(map, "name");
Integer age = MapUtils.getInt(map, "age");


Jackson으로 Map<String, Object> → 객체 매핑

Jackson 라이브러리Map을 객체로 손쉽게 변환할 수 있게 도와줍니다.

2-1. POJO 클래스 예시

public class User {
    private String name;
    private int age;
    private boolean isMember;

    // getter, setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public boolean isMember() { return isMember; }
    public void setMember(boolean member) { isMember = member; }
}

2-2. Map → 객체 변환 코드

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.LinkedHashMap;

public class MapToObjectExample {
    public static void main(String[] args) throws Exception {
        LinkedHashMap<String, Object> map = new LinkedHashMap<>();
        map.put("name", "홍길동");
        map.put("age", 30);
        map.put("member", true);  // 필드명 주의: isMember → member

        ObjectMapper mapper = new ObjectMapper();
        User user = mapper.convertValue(map, User.class);

        System.out.println(user.getName());     // 홍길동
        System.out.println(user.getAge());      // 30
        System.out.println(user.isMember());    // true
    }
}

⚠️ 필드명이 맞아야 함 (memberisMember는 setter 이름 따라감). 필요시 @JsonProperty 사용 가능.


📦 Jackson 라이브러리 설치 (Maven)

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.3</version> <!-- 최신 버전 확인 -->
</dependency>


✅ 1. Map → JSON → 객체 변환

Jackson은 중간에 JSON 문자열을 거쳐서 객체로 변환할 수도 있습니다.

예제

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.*;

public class MapToJsonToObject {
    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "홍길동");
        map.put("age", 30);

        ObjectMapper mapper = new ObjectMapper();

        // Map → JSON 문자열
        String jsonString = mapper.writeValueAsString(map);
        System.out.println("JSON: " + jsonString);

        // JSON 문자열 → 객체
        User user = mapper.readValue(jsonString, User.class);
        System.out.println("이름: " + user.getName());
        System.out.println("나이: " + user.getAge());
    }
}

User 클래스는 앞선 예시와 동일


✅ 2. 중첩된 구조 (Deep Object Mapping)

예시 JSON 구조:

{
  "name": "홍길동",
  "age": 30,
  "address": {
    "city": "서울",
    "zip": "12345"
  }
}

2-1. POJO 클래스 정의

public class Address {
    private String city;
    private String zip;

    // getters and setters
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }

    public String getZip() { return zip; }
    public void setZip(String zip) { this.zip = zip; }
}
public class User {
    private String name;
    private int age;
    private Address address;

    // getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
}

2-2. Map → 객체 매핑 (중첩 포함)

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashMap;
import java.util.Map;

public class DeepMapToObject {
    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "홍길동");
        map.put("age", 30);

        Map<String, Object> addressMap = new HashMap<>();
        addressMap.put("city", "서울");
        addressMap.put("zip", "12345");

        map.put("address", addressMap);

        ObjectMapper mapper = new ObjectMapper();
        User user = mapper.convertValue(map, User.class);

        System.out.println("이름: " + user.getName());
        System.out.println("도시: " + user.getAddress().getCity());
    }
}

✅ 3. Map/List 섞인 복합 구조 처리도 가능

{
  "user": {
    "name": "홍길동",
    "roles": ["admin", "user"]
  }
}

이 경우도 List<String>으로 필드를 만들고 처리 가능합니다.


📌 보너스: JSON 필드명이 Java 필드명과 다를 때

import com.fasterxml.jackson.annotation.JsonProperty;

public class User {
    @JsonProperty("full_name")
    private String name;

    // ...
}

객체 → JSON 문자열

ObjectMapper mapper = new ObjectMapper();

User user = new User();
user.setName("홍길동");
user.setAge(30);

String json = mapper.writeValueAsString(user);
System.out.println(json);

출력:

{“name”:”홍길동”,”age”:30}


✅ 2. 객체 → Map 변환

Map<String, Object> map = mapper.convertValue(user, new TypeReference<Map<String, Object>>() {});
System.out.println(map);

출력:

{name=홍길동, age=30}


배열/컬렉션 구조 매핑

예시 JSON:

[ {"name": "홍길동", "age": 30}, {"name": "이순신", "age": 45} ]

Java 코드

String jsonArray = "[{\"name\":\"홍길동\",\"age\":30},{\"name\":\"이순신\",\"age\":45}]";

List<User> userList = mapper.readValue(jsonArray, new TypeReference<List<User>>() {});
for (User u : userList) {
    System.out.println(u.getName() + ", 나이: " + u.getAge());
}


JsonNode (트리 기반 접근)

JsonNode는 동적 구조나 필드 존재 여부 등을 확인할 때 유용합니다.

예제 JSON

{
  "user": {
    "name": "홍길동",
    "age": 30
  },
  "active": true
}

Java 코드

String json = "{\"user\":{\"name\":\"홍길동\",\"age\":30},\"active\":true}";

JsonNode root = mapper.readTree(json);

String name = root.path("user").path("name").asText();
int age = root.path("user").path("age").asInt();
boolean active = root.path("active").asBoolean();

System.out.println("이름: " + name);
System.out.println("나이: " + age);
System.out.println("활성: " + active);

path()는 존재하지 않는 키를 접근해도 null 에러를 던지지 않음
get()null을 반환할 수 있으므로 주의


✅ 요약

기능메서드
객체 → JSONwriteValueAsString()
객체 → MapconvertValue()
JSON 배열 → ListreadValue(..., TypeReference<>)
JSON 트리 접근readTree() + JsonNode


Jackson을 활용한 고급 기능들

  1. JsonNode를 활용한 필터링/재구성
  2. ✅ 고급 직렬화 옵션: @JsonView, @JsonIgnore, @JsonInclude

를 순서대로 예제 중심으로 정리해드릴게요.


✅ 1. JsonNode로 JSON 필터링/재구성

📌 예제 JSON

{
  "id": 1,
  "name": "홍길동",
  "password": "secret",
  "roles": ["admin", "user"]
}

필터링: 특정 필드 제거 후 재구성

String json = "{\"id\":1,\"name\":\"홍길동\",\"password\":\"secret\",\"roles\":[\"admin\",\"user\"]}";

ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);

// 필드 제거
((ObjectNode) root).remove("password");

// JSON 재생성
String filteredJson = mapper.writeValueAsString(root);
System.out.println(filteredJson);

출력:

{"id":1,"name":"홍길동","roles":["admin","user"]}


특정 필드만 추출해서 새 JSON 만들기

ObjectNode filtered = mapper.createObjectNode();
filtered.set("name", root.get("name"));
filtered.set("roles", root.get("roles"));

String newJson = mapper.writeValueAsString(filtered);
System.out.println(newJson);

✅ 2. @JsonIgnore – 특정 필드 직렬화 제외

public class User {
    private String name;
    
    @JsonIgnore
    private String password;

    // getters and setters
}
User user = new User();
user.setName("홍길동");
user.setPassword("secret");

System.out.println(mapper.writeValueAsString(user));

출력:

{“name”:”홍길동”}


✅ 3. @JsonInclude – null 값/기본값 제외 설정

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    private String name;
    private String email; // null 일 수 있음

    // getters and setters
}

null인 필드는 출력되지 않음

다른 옵션들

옵션설명
NON_NULLnull 제외
NON_EMPTY빈 컬렉션, 빈 문자열 제외
NON_DEFAULT기본값 제외 (int = 0 등)
ALWAYS항상 출력 (기본값)

✅ 4. @JsonView – 조건에 따라 필드 선택 출력

뷰 클래스 정의

public class Views {
    public static class Public {}
    public static class Internal extends Public {}
}

모델 클래스 적용

public class User {
    @JsonView(Views.Public.class)
    private String name;

    @JsonView(Views.Internal.class)
    private String password;

    // getters and setters
}

직렬화 사용

User user = new User();
user.setName("홍길동");
user.setPassword("secret");

// Public view → password 제외
String jsonPublic = mapper
    .writerWithView(Views.Public.class)
    .writeValueAsString(user);

// Internal view → password 포함
String jsonInternal = mapper
    .writerWithView(Views.Internal.class)
    .writeValueAsString(user);

@JsonView는 API 응답에서 역할별 권한 필드 출력 등에 유용합니다.


요약

기능설명
JsonNodeJSON을 트리처럼 동적으로 다루기
@JsonIgnore특정 필드 직렬화/역직렬화 제외
@JsonIncludenull/빈값 기본값 포함 여부 설정
@JsonView특정 상황(view)에 따라 필드 다르게 출력

조건부 필드 출력 설정 – @JsonView 활용 API 응답 DTO 구성

🔹 DTO 구성

public class Views {
    public static class Public {}
    public static class Admin extends Public {}
}

public class UserDto {
    @JsonView(Views.Public.class)
    private String name;

    @JsonView(Views.Public.class)
    private String email;

    @JsonView(Views.Admin.class)
    private String ssn;

    // getters and setters
}

🔹 컨트롤러 응답 예시

@GetMapping("/user/public")
@JsonView(Views.Public.class)
public UserDto getPublicUser() {
    return userService.getUserDto();
}

@GetMapping("/user/admin")
@JsonView(Views.Admin.class)
public UserDto getAdminUser() {
    return userService.getUserDto();
}

Public API에서는 ssn 제외, Admin API에서는 포함.


✅ 2. 보안 마스킹 전략 – 필드 단위 마스킹 커스텀

🔹 마스킹 어노테이션 정의

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Mask {
    MaskType type() default MaskType.NAME;
}

public enum MaskType {
    NAME, EMAIL, PHONE
}

🔹 Jackson Serializer 구현

public class MaskingSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        Mask mask = (Mask) serializers.getAttribute("mask");
        if (mask != null && value != null) {
            switch (mask.type()) {
                case NAME:
                    value = value.charAt(0) + "*".repeat(value.length() - 1);
                    break;
                case EMAIL:
                    value = value.replaceAll("(?<=.).(?=.*@)", "*");
                    break;
                case PHONE:
                    value = value.replaceAll(".(?=.{4})", "*");
                    break;
            }
        }
        gen.writeString(value);
    }
}

🔹 DTO에 적용

public class SecureUserDto {
    private String name;

    @Mask(type = MaskType.EMAIL)
    private String email;

    // getters and setters
}

🔹 Jackson 설정 시 컨텍스트 주입 필요 (ObjectMapper 커스터마이징)

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new MaskingSerializer());
mapper.registerModule(module);

✅ 3. JsonNode 기반 필드 자동 필터링

시나리오: 민감 정보 필드를 리스트로 지정해 자동 제거

🔹 유틸리티 메서드

public static JsonNode filterSensitiveFields(JsonNode node, List<String> sensitiveKeys) {
    if (node.isObject()) {
        ObjectNode obj = (ObjectNode) node;
        for (String key : sensitiveKeys) {
            obj.remove(key);
        }
        for (Iterator<Map.Entry<String, JsonNode>> it = obj.fields(); it.hasNext(); ) {
            Map.Entry<String, JsonNode> entry = it.next();
            filterSensitiveFields(entry.getValue(), sensitiveKeys);
        }
    } else if (node.isArray()) {
        for (JsonNode item : node) {
            filterSensitiveFields(item, sensitiveKeys);
        }
    }
    return node;
}

🔹 사용 예시

String json = "{\"name\":\"홍길동\",\"ssn\":\"123456-1234567\",\"email\":\"hong@example.com\"}";
JsonNode root = mapper.readTree(json);

List<String> sensitiveFields = List.of("ssn");
JsonNode filtered = filterSensitiveFields(root, sensitiveFields);

System.out.println(mapper.writeValueAsString(filtered));

🔒 실전에서 조합 전략 예시

  • @JsonView역할 기반 API 필드 제어
  • @Mask필드 개별 마스킹
  • JsonNode 유틸로 동적 마스킹 or 필터링

💡 보안 + 직렬화 설계 팁

목적전략
API 응답 권한 제어@JsonView or DTO 분리
마스킹 처리@Mask + 커스텀 Serializer
동적 필터링JsonNode 트리 순회로 키 삭제
예외 발생 방지Optional, @JsonInclude, null-safe 접근


Jackson의 고급 기능을 활용한 보안 마스킹 전략


✅ 마스킹 전략 고급 적용법

  1. AOP 기반 마스킹 적용
  2. Jackson MixIn
  3. Jackson PropertyFilter + BeanPropertyWriter

✅ 1. AOP 기반 마스킹 전략

📌 목적:

  • DTO를 가공하지 않고, 컨트롤러 리턴 시 공통 마스킹 적용
  • 로그/응답 전처리에 적합

🔹 AOP 마스킹 예시 (응답 전처리)

@Aspect
@Component
public class ResponseMaskingAspect {

    @Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.PostMapping)")
    public Object maskResponse(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(result);

        JsonNode root = mapper.readTree(json);
        List<String> sensitiveFields = List.of("ssn", "password");

        maskJsonNode(root, sensitiveFields);
        return mapper.treeToValue(root, Object.class);
    }

    private void maskJsonNode(JsonNode node, List<String> fields) {
        if (node.isObject()) {
            ObjectNode obj = (ObjectNode) node;
            for (String key : fields) {
                if (obj.has(key)) obj.put(key, "****");
            }
            obj.fields().forEachRemaining(entry -> maskJsonNode(entry.getValue(), fields));
        } else if (node.isArray()) {
            for (JsonNode item : node) {
                maskJsonNode(item, fields);
            }
        }
    }
}

@RestControllerAdvice와 조합하면, 모든 응답에 자동 적용도 가능


✅ 2. Jackson MixIn – 외부 클래스에 @JsonIgnore/@JsonProperty 등 적용

📌 목적:

  • 소스 수정 없이 라이브러리 클래스 직렬화 제어

🔹 원본 클래스 (수정 불가)

public class ExternalUser {
    public String username;
    public String password;
}

🔹 MixIn 정의

public abstract class UserMixIn {
    @JsonIgnore
    public String password;
}

🔹 적용

ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(ExternalUser.class, UserMixIn.class);

String json = mapper.writeValueAsString(new ExternalUser("kim", "secret"));
// 출력: {"username":"kim"}

매우 유용한 기능 – 외부 DTO 응답 수정 없이 필터링 가능


✅ 3. PropertyFilter + BeanPropertyWriter – 동적 마스킹 전략

📌 목적:

  • 동적 조건(예: 사용자 권한)에 따라 필드를 숨기거나 마스킹

🔹 필터 정의

public class MaskingPropertyFilter extends SimpleBeanPropertyFilter {
    private final Set<String> sensitiveFields;

    public MaskingPropertyFilter(Set<String> sensitiveFields) {
        this.sensitiveFields = sensitiveFields;
    }

    @Override
    public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov, PropertyWriter writer) throws Exception {
        if (sensitiveFields.contains(writer.getName())) {
            gen.writeStringField(writer.getName(), "****");
        } else {
            writer.serializeAsField(bean, gen, prov);
        }
    }
}

🔹 적용 예시

FilterProvider filters = new SimpleFilterProvider()
    .addFilter("maskFilter", new MaskingPropertyFilter(Set.of("ssn", "password")));

ObjectMapper mapper = new ObjectMapper();
mapper.setFilterProvider(filters);

UserDto user = new UserDto("홍길동", "123456-1234567", "hong@example.com");

String json = mapper
    .writerWithDefaultPrettyPrinter()
    .writeValueAsString(user);
System.out.println(json);

DTO 클래스에 반드시 @JsonFilter("maskFilter") 필요

@JsonFilter("maskFilter")
public class UserDto {
    private String name;
    private String ssn;
    private String email;
}

🧠 언제 어떤 전략을 써야 하나요?

상황추천 방식
DTO 직접 수정 가능@JsonIgnore, @JsonInclude, @JsonView
외부 클래스 직렬화 제어 필요MixIn 사용
필드를 조건부로 마스킹/숨김PropertyFilter or AOP
JSON 전체에 유연한 트리 처리 필요JsonNode 유틸리티
응답 전처리/보안 필터 일괄 적용AOP + JsonNode 조합

💬 추가 가능 항목

  • 요청/응답 로그 마스킹 (logback custom encoder)
  • 마스킹 대상 필드 자동 탐지 (예: @Sensitive)
  • 동적 보안 정책 연동 (예: 사용자 등급별 마스킹 필드 다르게)

실전 프로젝트 기반 설정 예시나, 스프링 부트에서 필드 단위 보안 로깅 처리

error: Content is protected !!