Light Blue Pointer
본문 바로가기
TIL(CS)

TypeToken

by 개발바닥곰발바닥!!! 2025. 9. 3.

Gson TypeToken 개념 문제 (문제편)

문제 1: 기본 개념 (난이도: ★☆☆)

다음 중 TypeToken이 필요한 이유로 가장 적절한 것은?

  1. JSON 파싱 속도를 향상시키기 위해 ***** 2. Java의 Type Erasure로 인해 런타임에 제네릭 타입 정보가 사라지기 때문에 -> 이거
  2. 메모리 사용량을 줄이기 위해
  3. 스레드 안전성을 보장하기 위해

문제 2: 코드 분석 (난이도: ★★☆)

다음 코드에서 잘못된 부분을 찾아 수정하시오.

String json = "[\\"apple\\", \\"banana\\", \\"cherry\\"]";
Gson gson = new Gson();

// 잘못된 코드
List<String> fruits = gson.fromJson(json, List.class);

수정을 해 보았다~~

Type listType = new TypeToken<List<String>>(){}.getType();
List<String> fruits = gson.fromJson(json, listType);

List.class는 제네릭 타입 정보를 잃어버림 List로 처리하는데 그러면 String이 아닌 다른 객체로 변형될 수 있어버림~


문제 3: getType() 메서드 이해 (난이도: ★★☆)

다음 코드의 실행 결과를 예측하시오.

TypeToken<List<String>> token1 = new TypeToken<List<String>>(){};
TypeToken<List<Integer>> token2 = new TypeToken<List<Integer>>(){};

Type type1 = token1.getType();
Type type2 = token2.getType();

System.out.println("type1.equals(type2): " + type1.equals(type2));
System.out.println("type1 클래스: " + type1.getClass().getSimpleName());
System.out.println("type1 toString: " + type1);

getType()은 내부적으로 제네릭 파라미터까지 비교한다고 함 List과 List는 다르다고 판단


문제 4: Type 인터페이스 심화 (난이도: ★★★)

다음 코드에서 각 Type 객체가 어떤 구체적인 클래스의 인스턴스인지 예측하고, 그 이유를 설명하시오.

// Case 1: 단순 클래스
Type simpleType = String.class;

// Case 2: 제네릭 타입
Type genericType = new TypeToken<List<String>>(){}.getType();

// Case 3: 배열 타입
Type arrayType = String[].class;

// Case 4: 와일드카드 타입
Type wildcardType = new TypeToken<List<? extends Number>>(){}.getType();

System.out.println("Case 1: " + simpleType.getClass().getSimpleName());
System.out.println("Case 2: " + genericType.getClass().getSimpleName());
System.out.println("Case 3: " + arrayType.getClass().getSimpleName());
System.out.println("Case 4: " + wildcardType.getClass().getSimpleName());

모르겠길래 돌렸어요~

package kice4;

import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;
import java.util.List;

public class Main {

	public static void main(String[] args) {
		// Case 1: 단순 클래스
		Type simpleType = String.class;

		// Case 2: 제네릭 타입
		Type genericType = new TypeToken<List<String>>(){}.getType();

		// Case 3: 배열 타입
		Type arrayType = String[].class;

		// Case 4: 와일드카드 타입
		Type wildcardType = new TypeToken<List<? extends Number>>(){}.getType();

		System.out.println("Case 1: " + simpleType.getClass().getSimpleName());
		System.out.println("Case 2: " + genericType.getClass().getSimpleName());
		System.out.println("Case 3: " + arrayType.getClass().getSimpleName());
		System.out.println("Case 4: " + wildcardType.getClass().getSimpleName());

	}

}

결과

Case 1: Class
Case 2: ParameterizedTypeImpl
Case 3: Class
Case 4: ParameterizedTypeImpl

문제 5: getParameterized() 기본 사용법 (난이도: ★★☆)

다음 두 코드가 동일한 결과를 만드는지 확인하고, getParameterized() 메서드의 장점을 설명하시오.

// 방법 1: 전통적인 TypeToken 사용
Type type1 = new TypeToken<List<String>>(){}.getType();

// 방법 2: getParameterized() 사용
Type type2 = TypeToken.getParameterized(List.class, String.class).getType();

System.out.println("두 타입이 같은가? " + type1.equals(type2));

결과

두 타입이 같은가? true

Gson에서는 Java의 Type Erasure 때문에 제네릭 타입을 구분할 수 없음

TypeToken 또는 TypeToken.getParameterized() 를 사용하여 정확한 타입 정보를 만들어야 함~

⇒ 내부적으로 거의 같은 Type 객체를 생성하므로 결과는 true

getParameterized()

com.google.gson.reflect.TypeToken 클래스에서 제공하는 static 메서드

제너릭 타입의 Type 객체를 생성한다~

List<String> , Map<Sting,Integer> 같은 제너릭 타입 정보를 런타임에도 유지하도록 만들어줌

장점

1. 가독성

Type type = TypeToken.getParameterized(List.class, String.class).getType();
→ 한눈에 List<String> 구조라는 게 보임
Type type = new TypeToken<List<String>>(){}.getType();
→ 클래스 안에 중괄호가 들어가 가독성 떨어짐

2. 코드 간결

TypeToken.getParameterized(Map.class, String.class, Integer.class).getType();
→ 중첩 타입도 한 줄에 표현 가능
new TypeToken<Map<String, Integer>>(){}.getType();
→ 간단해 보이지만 중첩되면 불편함

복잡할수록 더 좋은듯

Type type = TypeToken.getParameterized(
    Map.class,
    String.class,
    TypeToken.getParameterized(List.class, Integer.class).getType()
).getType();
new TypeToken<Map<String, List<Integer>>>(){}.getType();

문제 6: 동적 타입 생성 (난이도: ★★★)

다음 메서드를 완성하시오. 이 메서드는 런타임에 클래스 정보를 받아서 List<T> 타입의 TypeToken을 생성해야 합니다.

public static <T> Type createListType(Class<T> elementClass) {
    // 여기를 완성하시오
     return TypeToken.getParameterized(List.class, elementClass).getType();
}

// 사용 예시
Type stringListType = createListType(String.class);
Type integerListType = createListType(Integer.class);


문제 7: 복잡한 중첩 타입 (난이도: ★★★)

Map<String, List<User>> 타입을 getParameterized()를 사용해서 생성하는 코드를 작성하시오.

public class User {
    private String name;
    private int age;
}

// getParameterized()를 사용해서 Map<String, List<User>> 타입 생성
Type listOfUser = TypeToken.getParameterized(List.class, User.class).getType();
Type mapOfList = TypeToken.getParameterized(Map.class, String.class, listOfUser).getType();


문제 8: Type 타입 검사 (난이도: ★★★)

주어진 Type 객체가 어떤 종류의 타입인지 판별하는 메서드를 작성하시오.

public static void analyzeType(Type type) {
    // type이 다음 중 어떤 것인지 판별하고 출력하시오:
    // 1. Class (단순 클래스)
    // 2. ParameterizedType (제네릭 타입)
    // 3. GenericArrayType (제네릭 배열)
    // 4. WildcardType (와일드카드)
    // 5. TypeVariable (타입 변수)
    if (type instanceof Class) {
        System.out.println("Class (단순 클래스)");
    } else if (type instanceof ParameterizedType) {
        System.out.println("ParameterizedType (제네릭 타입)");
    } else if (type instanceof GenericArrayType) {
        System.out.println("GenericArrayType (제네릭 배열)");
    } else if (type instanceof WildcardType) {
        System.out.println("WildcardType (와일드카드)");
    } else if (type instanceof TypeVariable) {
        System.out.println("TypeVariable (타입 변수)");
    } else {
        System.out.println("Unknown type");
    }
}

// 테스트할 타입들
Type[] testTypes = {
    String.class,
    new TypeToken<List<String>>(){}.getType(),
    new TypeToken<List<? extends Number>>(){}.getType(),
    new TypeToken<T[]>(){}.getType() // T는 타입 변수
};

결과

package kice4;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

import com.google.gson.reflect.TypeToken;

import java.util.List;

public class Main {

    public static void main(String[] args) {
        // 테스트할 타입들
        Type[] testTypes = {
            String.class, 
            new TypeToken<List<String>>(){}.getType(), 
            new TypeToken<List<? extends Number>>(){}.getType()
        };

        for (Type type : testTypes) {
            System.out.println("분석 대상: " + type.getTypeName());
            analyzeType(type);
            System.out.println();
        }
    }

    public static void analyzeType(Type type) {
        if (type instanceof Class) {
            System.out.println("Class (단순 클래스)");
        } else if (type instanceof ParameterizedType) {
            System.out.println("ParameterizedType (제네릭 타입)");
        } else if (type instanceof GenericArrayType) {
            System.out.println("GenericArrayType (제네릭 배열)");
        } else if (type instanceof WildcardType) {
            System.out.println("WildcardType (와일드카드)");
        } else if (type instanceof TypeVariable) {
            System.out.println("TypeVariable (타입 변수)");
        } else {
            System.out.println("Unknown type");
        }
    }
}

분석 대상: java.lang.String
Class (단순 클래스)

분석 대상: java.util.List<java.lang.String>
ParameterizedType (제네릭 타입)

분석 대상: java.util.List<? extends java.lang.Number>
ParameterizedType (제네릭 타입)

문제 9: getParameterized() 고급 활용 (난이도: ★★★)

다음 제네릭 메서드에서 getParameterized()를 사용해서 동적으로 API 응답 타입을 처리하는 코드를 완성하시오.

public class ApiClient {
    public static class ApiResponse<T> {
        private boolean success;
        private T data;
        private String message;
        // getter, setter 생략
    }

    // 이 메서드를 완성하시오
    public <T> ApiResponse<T> request(String url, Class<T> responseClass) {
        // JSON 응답을 받았다고 가정
        String jsonResponse = "...";

        Gson gson = new Gson();

        // getParameterized()를 사용해서 ApiResponse<T> 타입 생성
        Type type = TypeToken.getParameterized(ApiResponse.class, responseClass).getType();
        // 그리고 JSON을 파싱하여 반환
        return new Gson().fromJson(jsonResponse, type);
    }
}

고재원

    public static <T> ApiResponse<T> request(String url, Class<T> responseClass) {
        // JSON 응답을 받았다고 가정
        String jsonResponse = "{\\"name\\":\\"jason\\", \\"age\\":29}"; 
        
        Gson gson = new Gson();
        
        // getParameterized()를 사용해서 ApiResponse<T> 타입 생성
        Type responseType = TypeToken.getParameterized(responseClass).getType();
       
        // 그리고 JSON을 파싱하여 반환
        T data = gson.fromJson(jsonResponse, responseType);
        ApiResponse<T> response = new ApiResponse();
        response.setData(data);
        response.setMessage("tired");
        response.setSuccess(true);
        
        return response;
    }

문제 10: Type 객체 재사용과 성능 (난이도: ★★★)

다음 두 코드의 성능 차이를 분석하고, 최적화 방안을 제시하시오.

// 코드 A: 매번 새로 생성
public class Parser1 {
    public List<String> parseStringList(String json) {
        Type type = new TypeToken<List<String>>(){}.getType();
        return new Gson().fromJson(json, type);
    }
}

// 코드 B: 정적 필드 사용
public class Parser2 {
    private static final Type STRING_LIST_TYPE =
        new TypeToken<List<String>>(){}.getType();

    public List<String> parseStringList(String json) {
        return new Gson().fromJson(json, STRING_LIST_TYPE);
    }
}

// 코드 C: getParameterized() 사용
public class Parser3 {
    private static final Type STRING_LIST_TYPE =
        TypeToken.getParameterized(List.class, String.class).getType();

    public List<String> parseStringList(String json) {
        return new Gson().fromJson(json, STRING_LIST_TYPE);
    }
}

package kice4;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        String json = "[\\"apple\\", \\"banana\\", \\"cherry\\"]";
        int iterations = 1_000_000;

        // 코드 A: 매번 새로 생성
        Parser1 p1 = new Parser1();
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            p1.parseStringList(json);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("Parser1 : " + (end1 - start1) + "ms");

        // 코드 B: 정적 필드 사용
        Parser2 p2 = new Parser2();
        long start2 = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            p2.parseStringList(json);
        }
        long end2 = System.currentTimeMillis();
        System.out.println("Parser2 : " + (end2 - start2) + "ms");

        // 코드 C: 정적 필드 사용 + getParameterized() 사용
        Parser3 p3 = new Parser3();
        long start3 = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            p3.parseStringList(json);
        }
        long end3 = System.currentTimeMillis();
        System.out.println("Parser3 : " + (end3 - start3) + "ms");
    }
}

// 코드 A: 매번 새로 생성
class Parser1 {
    public List<String> parseStringList(String json) {
        Type type = new TypeToken<List<String>>(){}.getType();
        return new Gson().fromJson(json, type);
    }
}

// 코드 B: 정적 필드 사용
class Parser2 {
    private static final Type STRING_LIST_TYPE = new TypeToken<List<String>>(){}.getType();

    public List<String> parseStringList(String json) {
        return new Gson().fromJson(json, STRING_LIST_TYPE);
    }
}

// 코드 C: 정적 필드 사용 + getParameterized() 사용
class Parser3 {
    private static final Type STRING_LIST_TYPE = TypeToken.getParameterized(List.class, String.class).getType();

    public List<String> parseStringList(String json) {
        return new Gson().fromJson(json, STRING_LIST_TYPE);
    }
}

Parser1 : 2559ms
Parser2 : 2093ms
Parser3 : 1946ms

어떤 방식이 가장 효율적인지 설명하고, 그 이유를 서술하시오.

B,C) Type 객체는 정적으로 재사용하는 것이 좋음 반복적으로 같은 타입을 파싱할 경우 메모리와 성능에서 이점이 있음

C) 가독성 원탑… 반드시 저거를 해야 안 헷갈릴듯

Q. 왜 3번이 빠른지 궁금하다~


문제 11: 실무 문제 해결 (난이도: ★★★)

다음과 같은 복잡한 JSON 구조를 처리해야 합니다. 적절한 TypeToken을 생성하는 코드를 작성하시오.

{
  "users": {
    "admins": [
      {"name": "admin1", "permissions": ["read", "write", "delete"]},
      {"name": "admin2", "permissions": ["read", "write"]}
    ],
    "normal": [
      {"name": "user1", "permissions": ["read"]},
      {"name": "user2", "permissions": ["read"]}
    ]
  }
}

목표 타입: Map<String, Map<String, List<User>>>

public class User {
    private String name;
    private List<String> permissions;
    // getter, setter 생략
}

package kice4;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class Main {
    public static void main(String[] args) {
        String json = """
        {
          "users": {
            "admins": [
              {"name": "admin1", "permissions": ["read", "write", "delete"]},
              {"name": "admin2", "permissions": ["read", "write"]}
            ],
            "normal": [
              {"name": "user1", "permissions": ["read"]},
              {"name": "user2", "permissions": ["read"]}
            ]
          }
        }
        """;

        Gson gson = new Gson();

        // 목표 타입: Map<String, Map<String, List<User>>>
        Type type = TypeToken.getParameterized( Map.class, // Map<
            String.class, // String,
            TypeToken.getParameterized( Map.class, // Map<
                String.class, // String,
                TypeToken.getParameterized(List.class, // List<
	                User.class // User
	                ).getType() // >
            ).getType() // >
        ).getType(); // >

        Map<String, Map<String, List<User>>> parsed = gson.fromJson(json, type);

        // 출력
        Map<String, List<User>> users = parsed.get("users");

        for (Map.Entry<String, List<User>> entry : users.entrySet()) {
            System.out.println("[" + entry.getKey() + "]");
            for (User user : entry.getValue()) {
                System.out.println("- " + user.getName() + ": " + user.getPermissions());
            }
        }
    }

    public static class User {
        private String name;
        private List<String> permissions;

        public String getName() {
            return name;
        }

        public List<String> getPermissions() {
            return permissions;
        }
    }
}

        // 목표 타입: Map<String, Map<String, List<User>>>
        Type type = TypeToken.getParameterized( Map.class, // Map<
            String.class, // String,
            TypeToken.getParameterized( Map.class, // Map<
                String.class, // String,
                TypeToken.getParameterized(List.class, // List<
	                User.class // User
	                ).getType() // >
            ).getType() // >
        ).getType(); // >

이런식으로 작성하니까 덜헷갈린다~~ TypeToken.getParameterized() 작성 템플릿~~

[admins]
- admin1: [read, write, delete]
- admin2: [read, write]
[normal]
- user1: [read]
- user2: [read]

문제 12: 제네릭 메서드와 TypeToken (난이도: ★★★)

다음 제네릭 유틸리티 메서드를 완성하시오. 이 메서드는 임의의 Collection 타입을 동적으로 처리할 수 있어야 합니다.

public class JsonUtils {

    // 이 메서드를 완성하시오
    public static <T, C extends Collection<T>> C parseCollection(
        String json,
        Class<C> collectionClass,
        Class<T> elementClass) {

        // getParameterized()를 사용해서 동적으로 타입 생성
        Type type = TypeToken.getParameterized(collectionClass, elementClass).getType();
        // 예: List<String>, Set<Integer>, ArrayList<User> 등
        return new Gson().fromJson(json, type);
    }

    // 사용 예시:
    // List<String> list = parseCollection(json, List.class, String.class);
    // Set<Integer> set = parseCollection(json, Set.class, Integer.class);
}

package kice4;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.Collection;

public class JsonUtils {

    public static <T, C extends Collection<T>> C parseCollection(
            String json,
            Class<C> collectionClass,
            Class<T> elementClass) {

        Type type = TypeToken.getParameterized(collectionClass, elementClass).getType();
        return new Gson().fromJson(json, type);
    }
}

package kice4;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        // 예시 1: List<String>
        String json1 = "[\\"apple\\", \\"banana\\", \\"cherry\\"]";
        List<String> list = JsonUtils.parseCollection(json1, List.class, String.class);
        System.out.println("List<String>: " + list);

        // 예시 2: Set<Integer>
        String json2 = "[1, 2, 3, 2]";
        Set<Integer> set = JsonUtils.parseCollection(json2, Set.class, Integer.class);
        System.out.println("Set<Integer>: " + set);

        // 예시 3: List<User>
        String json3 = """
        [
          {"name": "Alice", "permissions": ["read", "write"]},
          {"name": "Bob", "permissions": ["read"]}
        ]
        """;
        List<User> users = JsonUtils.parseCollection(json3, List.class, User.class);
        System.out.println("List<User>:");
        for (User u : users) {
            System.out.println("- " + u.getName() + ": " + u.getPermissions());
        }
    }

    public static class User {
        private String name;
        private List<String> permissions;

        public String getName() {
            return name;
        }

        public List<String> getPermissions() {
            return permissions;
        }
    }
}

13. 추가로 Map도 공통유틸화?!

통합본(아래에 요약본 있음)

package kice4;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Gson gson = new Gson();

        // List<String>
        String json1 = "[\\"apple\\", \\"banana\\", \\"cherry\\"]";
        List<String> list = JsonUtils.parseCollection(json1, List.class, String.class);
        System.out.println("List<String>: " + list);

        // Set<Integer>
        String json2 = "[1, 2, 3, 2]";
        Set<Integer> set = JsonUtils.parseCollection(json2, Set.class, Integer.class);
        System.out.println("Set<Integer>: " + set);

        // List<User>
        String json3 = """
        [
          {"name": "Alice", "permissions": ["read", "write"]},
          {"name": "Bob", "permissions": ["read"]}
        ]
        """;
        List<User> users = JsonUtils.parseCollection(json3, List.class, User.class);
        System.out.println("List<User>:");
        for (User u : users) {
            System.out.println("- " + u.getName() + ": " + u.getPermissions());
        }

        // Map<String, Map<String, List<User>>>
        String json4 = """
        {
          "users": {
            "admins": [
              {"name": "admin1", "permissions": ["read", "write", "delete"]},
              {"name": "admin2", "permissions": ["read", "write"]}
            ],
            "normal": [
              {"name": "user1", "permissions": ["read"]},
              {"name": "user2", "permissions": ["read"]}
            ]
          }
        }
        """;

        Type complexType = TypeToken.getParameterized(
            Map.class, // Map<
            String.class, // String,
            TypeToken.getParameterized(
                Map.class, // Map<
                String.class, // String,
                TypeToken.getParameterized(List.class, User.class).getType() // List<User>
            ).getType() // >
        ).getType(); // >
        
        Map<String, Map<String, List<User>>> complexParsed = gson.fromJson(json4, complexType);
        
        System.out.println("\\n[users] JSON 구조:");
        for (Map.Entry<String, List<User>> group : complexParsed.get("users").entrySet()) {
            System.out.println("[" + group.getKey() + "]");
            for (User u : group.getValue()) {
                System.out.println("- " + u.getName() + ": " + u.getPermissions());
            }
        }
        
        Type innerValue = TypeToken.getParameterized(List.class, User.class).getType();
        Type nestedMapValue = TypeToken.getParameterized(Map.class, String.class, innerValue).getType();

        Map<String, Map<String, List<User>>> result = JsonUtils.parseMap(json4, String.class, nestedMapValue);
        
        System.out.println("\\n[users] JSON 구조 Util로 만들어봄~:");
        for (Map.Entry<String, Map<String, List<User>>> outer : result.entrySet()) {
            String topLevelKey = outer.getKey(); 
            Map<String, List<User>> groupMap = outer.getValue(); 

            System.out.println("[" + topLevelKey + "]");

            for (Map.Entry<String, List<User>> group : groupMap.entrySet()) {
                System.out.println("  [" + group.getKey() + "]");
                for (User u : group.getValue()) {
                    System.out.println("  - " + u.getName() + ": " + u.getPermissions());
                }
            }
        }

        
    }
}

package kice4;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

public class JsonUtils {

    public static <T, C extends Collection<T>> C parseCollection(
            String json,
            Class<C> collectionClass,
            Class<T> elementClass) {

        Type type = TypeToken.getParameterized(collectionClass, elementClass).getType();
        return new Gson().fromJson(json, type);
    }
    
    public static <K, V> Map<K, V> parseMap(String json, Class<K> keyClass, Type valueType) {
        Type type = TypeToken.getParameterized(Map.class, keyClass, valueType).getType();
        return new Gson().fromJson(json, type);
    }

}

공통유틸화 요약본

package kice4;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

public class JsonUtils {

    public static <T, C extends Collection<T>> C parseCollection(
            String json,
            Class<C> collectionClass,
            Class<T> elementClass) {

        Type type = TypeToken.getParameterized(collectionClass, elementClass).getType();
        return new Gson().fromJson(json, type);
    }
    
    public static <K, V> Map<K, V> parseMap(String json, Class<K> keyClass, Type valueType) {
        Type type = TypeToken.getParameterized(Map.class, keyClass, valueType).getType();
        return new Gson().fromJson(json, type);
    }

}

package kice4;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.*;

public class Main {
	public static void main(String[] args) {
		Gson gson = new Gson();

		// Map<String, Map<String, List<User>>>
		String json4 = """
				{
				  "users": {
				    "admins": [
				      {"name": "admin1", "permissions": ["read", "write", "delete"]},
				      {"name": "admin2", "permissions": ["read", "write"]}
				    ],
				    "normal": [
				      {"name": "user1", "permissions": ["read"]},
				      {"name": "user2", "permissions": ["read"]}
				    ]
				  }
				}
				""";

		// 1. 곧이곧대로 다 씀
		Type complexType = TypeToken.getParameterized(Map.class, // Map<
				String.class, // String,
				TypeToken.getParameterized(Map.class, // Map<
						String.class, // String,
						TypeToken.getParameterized(List.class, User.class).getType() // List<User>
				).getType() // >
		).getType(); // >

		Map<String, Map<String, List<User>>> complexParsed = gson.fromJson(json4, complexType);

		// 곧이곧대로 출력~
		System.out.println("\\n[users] JSON 구조:");
		for (Map.Entry<String, List<User>> group : complexParsed.get("users").entrySet()) {
			System.out.println("[" + group.getKey() + "]");
			for (User u : group.getValue()) {
				System.out.println("- " + u.getName() + ": " + u.getPermissions());
			}
		}

		// 2. Map 매핑도 유틸화해봄, 2중 Map이라 두번 해야함
		Type innerValue = TypeToken.getParameterized(List.class, User.class).getType();
		Type nestedMapValue = TypeToken.getParameterized(Map.class, String.class, innerValue).getType();

		Map<String, Map<String, List<User>>> result = JsonUtils.parseMap(json4, String.class, nestedMapValue);

		// 유틸화 출력
		System.out.println("\\n[users] JSON 구조 Util로 만들어봄~:");
		for (Map.Entry<String, Map<String, List<User>>> outer : result.entrySet()) {
			String topLevelKey = outer.getKey();
			Map<String, List<User>> groupMap = outer.getValue();

			System.out.println("[" + topLevelKey + "]");

			for (Map.Entry<String, List<User>> group : groupMap.entrySet()) {
				System.out.println("  [" + group.getKey() + "]");
				for (User u : group.getValue()) {
					System.out.println("  - " + u.getName() + ": " + u.getPermissions());
				}
			}
		}

		// 3. 굳이 유틸화를 해야하는가... 끊어서 쓰는 아이디어는 좋은거같음
		// 1단계: List<User>
		Type listOfUser = TypeToken.getParameterized(List.class, User.class).getType();

		// 2단계: Map<String, List<User>>
		Type innerMap = TypeToken.getParameterized(Map.class, String.class, listOfUser).getType();

		// 3단계: Map<String, Map<String, List<User>>>
		Type outerMap = TypeToken.getParameterized(Map.class, String.class, innerMap).getType();

		// 파싱
		Map<String, Map<String, List<User>>> result2 = new Gson().fromJson(json4, outerMap);

		// 끊어서 쓰는 것 출력
		System.out.println("\\n[users] JSON 구조 그냥 끊어서 써봄~:");
		for (Map.Entry<String, Map<String, List<User>>> outer : result2.entrySet()) {
			String topLevelKey = outer.getKey();
			Map<String, List<User>> groupMap = outer.getValue();

			System.out.println("[" + topLevelKey + "]");

			for (Map.Entry<String, List<User>> group : groupMap.entrySet()) {
				System.out.println("  [" + group.getKey() + "]");
				for (User u : group.getValue()) {
					System.out.println("  - " + u.getName() + ": " + u.getPermissions());
				}
			}
		}

	}
}

[users] JSON 구조:
[admins]
- admin1: [read, write, delete]
- admin2: [read, write]
[normal]
- user1: [read]
- user2: [read]

[users] JSON 구조 Util로 만들어봄~:
[users]
  [admins]
  - admin1: [read, write, delete]
  - admin2: [read, write]
  [normal]
  - user1: [read]
  - user2: [read]

[users] JSON 구조 그냥 끊어서 써봄~:
[users]
  [admins]
  - admin1: [read, write, delete]
  - admin2: [read, write]
  [normal]
  - user1: [read]
  - user2: [read]

'TIL(CS)' 카테고리의 다른 글

이미지 처리  (0) 2025.09.03
궁극의 제티서버  (0) 2025.09.03
웹소켓이란? (feat.Spring,STOMP,SockJS)  (1) 2024.10.26
OOP의 SOLID 원칙  (2) 2024.05.28
RDBMS의 ACID 트랜잭션과 NoSQL의 BASE 속성  (0) 2024.05.28