<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>JSON 다루기 튜토리얼 — 데이터스케쳐스 위키</title>
  <meta name="description" content="JSON 다루기 튜토리얼 Go 프로그램에서 JSON 데이터를 읽고 쓰는 일은 생각보다 아주 쉽습니다. encoding/json/v2 패키지가 그걸 해 주거든요. 이 패키지는 예전 encoding/json 패키지보다 더 깔끔한 API와 더 좋은 기본값을 제공합니다. JS" />
  
  <link rel="canonical" href="https://data-sketchers.com/wiki/tech-stack.languages.go.json" />
  

  <!-- Hreflang (KO/EN + x-default) — 대안 언어는 koUrl/enUrl(없으면 생략) -->
  <link rel="alternate" hreflang="ko" href="https://data-sketchers.com/wiki/tech-stack.languages.go.json" />
  <link rel="alternate" hreflang="en" href="https://data-sketchers.com/en/wiki/tech-stack.languages.go.json" />
  <link rel="alternate" hreflang="x-default" href="https://data-sketchers.com/wiki/tech-stack.languages.go.json" />

  <!-- Open Graph -->
  <meta property="og:type" content="website" />
  <meta property="og:site_name" content="DataSketchers" />
  <meta property="og:title" content="JSON 다루기 튜토리얼 — 데이터스케쳐스 위키" />
  <meta property="og:description" content="JSON 다루기 튜토리얼 Go 프로그램에서 JSON 데이터를 읽고 쓰는 일은 생각보다 아주 쉽습니다. encoding/json/v2 패키지가 그걸 해 주거든요. 이 패키지는 예전 encoding/json 패키지보다 더 깔끔한 API와 더 좋은 기본값을 제공합니다. JS" />
  <meta property="og:url" content="https://data-sketchers.com/wiki/tech-stack.languages.go.json" />
  <meta property="og:image" content="https://data-sketchers.com/assets/logo-black.png" />
  <meta property="og:image:width" content="2172" />
  <meta property="og:image:height" content="724" />
  <meta property="og:image:alt" content="JSON 다루기 튜토리얼 — 데이터스케쳐스 위키" />
  <meta property="og:locale" content="ko_KR" />
  <meta property="og:locale:alternate" content="en_US" />

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="JSON 다루기 튜토리얼 — 데이터스케쳐스 위키" />
  <meta name="twitter:description" content="JSON 다루기 튜토리얼 Go 프로그램에서 JSON 데이터를 읽고 쓰는 일은 생각보다 아주 쉽습니다. encoding/json/v2 패키지가 그걸 해 주거든요. 이 패키지는 예전 encoding/json 패키지보다 더 깔끔한 API와 더 좋은 기본값을 제공합니다. JS" />
  <meta name="twitter:image" content="https://data-sketchers.com/assets/logo-black.png" />
  <meta name="twitter:image:alt" content="JSON 다루기 튜토리얼 — 데이터스케쳐스 위키" />

  <!-- Organization JSON-LD — th:inline 금지(파싱 오류): 사이트 url 을 컨트롤러가 organizationJson 으로 주입.
       주입이 없으면 고정 생략(페이지별 통합 시 site/json 주입). -->
  <script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"DataSketchers","url":"https://data-sketchers.com","logo":"https://data-sketchers.com/assets/logo-black.png","sameAs":["https://github.com/data-sketchers"]}</script>
  <!-- 페이지별 커스텀 JSON-LD (BlogPosting/Article/BreadcrumbList) — 호출측에서 pageJson 통째로 주입 -->
  <script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":"1","name":"Home","item":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/"},{"@type":"ListItem","position":"2","name":"tech-stack.languages.go.json","item":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/tech-stack.languages.go.json"}]},{"@type":"Article","headline":"JSON 다루기 튜토리얼","description":"JSON 다루기 튜토리얼 Go 프로그램에서 JSON 데이터를 읽고 쓰는 일은 생각보다 아주 쉽습니다. encoding/json/v2 패키지가 그걸 해 주거든요. 이 패키지는 예전 encoding/json 패키지보다 더 깔끔한 API와 더 좋은 기본값을 제공합니다. JS","mainEntityOfPage":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/tech-stack.languages.go.json","url":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/tech-stack.languages.go.json","dateModified":"2026-09-13T01:53:04.049137Z"}]}</script>

  <link rel="icon" type="image/png" href="/assets/favicon.png" />
  <link rel="preconnect" href="https://cdn.jsdelivr.net" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&display=swap" />
  <link rel="stylesheet" href="/ds/tokens.css" />
  <!-- 원본 디자인 시스템 재현 (기존 대비 95%+ 목표) -->
  <link rel="stylesheet" href="/ds/base.css" />
  <link rel="stylesheet" href="/ds/components.css" />
  <link rel="stylesheet" href="/ds/post.css" />
  <link rel="stylesheet" href="/ds/effects.css" />
  <script src="/ds/effects.js" defer></script>
  <script src="/ds/threads.js" defer></script>
</head>
<body>
  <html>
<header id="site-header" class="site-header">
  <div class="site-header__inner">
    <a href="/" class="site-header__logo" aria-label="DataSketchers 홈" data-logo-host>
      <img class="site-header__logo-word" src="/assets/logo-data-sketchers-light-bg.png" alt="" width="153" height="51" style="height:48px;width:auto" />
    </a>
    <nav class="site-header__nav" aria-label="주 메뉴">
      <!-- DB 관리 헤더 메뉴 (site_menu source-of-truth). 항목이 children 을 가지면 서브메뉴 렌더 -->
      <div class="site-header__nav-wrap">
        
        
          <a href="/about" class="site-header__nav-item">회사소개</a>
        
      </div>
      <div class="site-header__nav-wrap">
        
        
          <a href="/blog" class="site-header__nav-item">블로그</a>
        
      </div>
    </nav>
    <div class="site-header__utils">
      <button class="site-header__menu-btn" data-menu-open aria-label="전체 메뉴 열기" aria-expanded="false">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
          <line x1="4" y1="8" x2="20" y2="8" /><line x1="4" y1="16" x2="20" y2="16" />
        </svg>
      </button>
    </div>
  </div>
</header>

<!-- 전체 메뉴(햄버거) 풀 스크린 오버레이 — 데스크톱·모바일 모두 동작. hamburger_menu (DB) 기반 -->
<div class="site-menu-overlay" data-menu-overlay aria-hidden="true">
  <div class="site-menu-overlay__scrim" data-menu-close></div>
  <div class="site-menu-overlay__panel" role="dialog" aria-modal="true" aria-label="전체 메뉴">
    <div class="site-menu-overlay__head">
      <a href="/" class="site-menu-overlay__logo" data-menu-close>
        <img src="/assets/logo-data-sketchers-light-bg.png" alt="DataSketchers" width="132" height="44" style="height:36px;width:auto" />
      </a>
      <button class="site-menu-overlay__close" data-menu-close aria-label="전체 메뉴 닫기">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
      </button>
    </div>
    <nav class="site-menu-overlay__nav" aria-label="전체 메뉴 목록">
      <h2 class="site-menu-overlay__title">전체 메뉴</h2>
      <div class="site-menu-overlay__group">
        <a href="/about" class="site-menu-overlay__group-title"
           data-menu-close>회사소개</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/services" class="site-menu-overlay__group-title"
           data-menu-close>사업영역</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/products" class="site-menu-overlay__group-title"
           data-menu-close>D-SKET</a>
        <div class="site-menu-overlay__subs">
          <a href="/products/webbuilder"
             class="site-menu-overlay__sub" data-menu-close>웹빌더</a><a href="/products/canvas"
             class="site-menu-overlay__sub" data-menu-close>Canvas</a><a href="/products/events"
             class="site-menu-overlay__sub" data-menu-close>Events</a>
        </div>
      </div>
      <div class="site-menu-overlay__group">
        <a href="/clients" class="site-menu-overlay__group-title"
           data-menu-close>고객사</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/blog" class="site-menu-overlay__group-title"
           data-menu-close>블로그</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/" class="site-menu-overlay__group-title"
           data-menu-close>위키</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/press" class="site-menu-overlay__group-title"
           data-menu-close>보도자료</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/esg" class="site-menu-overlay__group-title"
           data-menu-close>ESG</a>
        
      </div>
    </nav>
  </div>
</div>
</html>

  <main class="ds-container" style="padding-top:calc(var(--header-h) + 24px);">
    <div class="wiki-layout" style="padding-top:0;">   <!-- layout CSS(columns/gap) 는 ds/effects.css 의 .wiki-layout 가 담당 -->

      <!-- ═══ 왼쪽: scope tree (재귀, 접기/펼치기) ═══ -->
      <aside id="wiki-scope" class="wiki-scope">
        <div style="padding:12px 12px 10px; font-family:var(--font-mono); font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--ds-text-3);">Topics</div>
        <!-- 조상 breadcrumb (root=현재 페이지이므로 위로 이동 경로) -->
        <nav aria-label="위키 경로" style="display:flex; flex-wrap:wrap; gap:4px 2px; align-items:center; padding:0 12px 10px; border-bottom:1px solid var(--ds-line); margin-bottom:8px;">
          <a href="/wiki/tech-stack" style="font-size:11px; color:var(--ds-text-3); text-decoration:none; white-space:nowrap;">기술 스택</a><a href="/wiki/tech-stack.languages" style="font-size:11px; color:var(--ds-text-3); text-decoration:none; white-space:nowrap;">언어 · 런타임</a><a href="/wiki/tech-stack.languages.go" style="font-size:11px; color:var(--ds-text-3); text-decoration:none; white-space:nowrap;">Go</a>
          <span style="color:var(--ds-line-strong); font-size:10px;">›</span><span style="color:var(--ds-line-strong); font-size:10px;">›</span><span style="color:var(--ds-line-strong); font-size:10px;">›</span>
        </nav>
        <nav aria-label="위키 주제 트리" style="display:flex; flex-direction:column; gap:2px;">
          <ul class="wiki-tree" style="list-style:none; margin:0; padding:0;">
    
      <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      <button type="button" class="wiki-tree__toggle" data-wtree-toggle
              aria-label="하위 토글" aria-expanded="true">▾</button>
      

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go">Go</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    <ul class="wiki-tree"
        style="list-style:none; margin:0; padding:0 0 0 14px;">
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.build-cover">통합 테스트용 커버리지 프로파일링 지원</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.comment">Go 문서 주석</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.create-module">Tutorial: Go 모듈 만들기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-access">튜토리얼: 관계형 데이터베이스 접근하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-cancel-operations">진행 중인 작업 취소하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-change-data">데이터를 반환하지 않는 SQL 문 실행</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-execute-transactions">트랜잭션 실행하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-index">관계형 데이터베이스 접근</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-manage-connections">연결 관리하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-open-handle">데이터베이스 핸들 열기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-prepared-statements">준비된 문 사용하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-querying">데이터 조회하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.database-sql-injection">SQL 인젝션 위험 피하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.diagnostics">진단</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.errors">Go의 에러 처리: error 값, panic, recover</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.fuzz">튜토리얼: 퍼징 시작하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.gc-guide">Go 가비지 컬렉터 가이드</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.generics-tutorial">Tutorial: Go 제네릭 시작하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.getting-started">Tutorial: Go 시작하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.gomod-reference">go.mod 파일 레퍼런스</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.goroutines-channels">고루틴과 채널로 다루는 동시성</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.interfaces">Go의 인터페이스: 행동으로 타입을 설계하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node is-active" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link is-active"
         href="/wiki/tech-stack.languages.go.json">JSON 다루기 튜토리얼</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-developing">모듈 개발하고 게시하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-layout">Go 모듈 구성하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-major-version">메이저 버전 업데이트 개발하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-managing-dependencies">의존성 관리하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-managing-source">모듈 소스 관리하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-publishing">모듈 배포하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-release-workflow">모듈 릴리스·버전 관리 워크플로</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.modules-version-numbers">모듈 버전 번호</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.packages-modules">Go 패키지와 모듈로 코드 조직화하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.pgo">프로파일 기반 최적화</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.race-detector">Go 경쟁 상태 탐지기(Data Race Detector)로 동시성 버그 잡기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.web-service-gin">튜토리얼: Go와 Gin으로 RESTful API 개발하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.languages.go.workspaces">Tutorial: 멀티 모듈 워크스페이스 시작하기</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
    </ul>
  </li>
    
  </ul>
        </nav>
      </aside>

      <!-- ═══ 중앙: 콘텐츠 ═══ -->
      <article class="ds-doc" style="min-width:0;">
        <nav class="ds-breadcrumb" aria-label="breadcrumb" style="padding:12px 0 16px; font-size:12.5px; color:var(--ds-text-3);">
          <a href="/wiki/tech-stack" style="color:inherit; text-decoration:none;">기술 스택</a><a href="/wiki/tech-stack.languages" style="color:inherit; text-decoration:none;">언어 · 런타임</a><a href="/wiki/tech-stack.languages.go" style="color:inherit; text-decoration:none;">Go</a>
          <span style="color:var(--ds-line-strong);"> › </span><span style="color:var(--ds-line-strong);"> › </span><span style="color:var(--ds-line-strong);"> › </span>
        </nav>
        <h1 style="margin-bottom:20px;">JSON 다루기 튜토리얼</h1>
        <!-- 모바일 전용: 목차를 본문 상단(제목 하단)에 항상 펼침 -->
        <nav aria-label="페이지 목차" class="wiki-toc-mobile"
             style="display:none; border-bottom:1px solid var(--ds-line); padding-bottom:12px; margin-bottom:24px;">
          <div class="ds-label" style="font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--ds-text-3); margin-bottom:8px;">목차</div>
          <div style="display:flex; flex-direction:column; gap:2px;">
            <a href="#json-다루기-튜토리얼" style="padding-left:0px">JSON 다루기 튜토리얼</a><a href="#인코딩-encoding" style="padding-left:0px">인코딩 (Encoding)</a><a href="#디코딩-decoding" style="padding-left:0px">디코딩 (Decoding)</a><a href="#any로-제네릭하게-다루기" style="padding-left:0px">any로 제네릭하게 다루기</a><a href="#임의의-데이터-디코딩하기-decoding-arbitrary-data" style="padding-left:0px">임의의 데이터 디코딩하기 (Decoding arbitrary data)</a><a href="#참조-타입-reference-types" style="padding-left:0px">참조 타입 (Reference Types)</a><a href="#스트리밍-marshal과-unmarshal" style="padding-left:0px">스트리밍 Marshal과 Unmarshal</a><a href="#커스텀-marshal과-unmarshal" style="padding-left:0px">커스텀 Marshal과 Unmarshal</a><a href="#참고-자료-references" style="padding-left:0px">참고 자료 (References)</a><a href="#더-알아보기-learn-more" style="padding-left:0px">더 알아보기 (Learn more)</a>
          </div>
        </nav>
        <div><h2 id="json-다루기-튜토리얼">JSON 다루기 튜토리얼</h2>
<p>Go 프로그램에서 JSON 데이터를 읽고 쓰는 일은 생각보다 아주 쉽습니다. <code>encoding/json/v2</code> 패키지가 그걸 해 주거든요. 이 패키지는 예전 <code>encoding/json</code> 패키지보다 더 깔끔한 API와 더 좋은 기본값을 제공합니다. JSON의 기본 개념부터, Go 자료구조를 JSON으로 바꾸고(인코딩), JSON을 Go 자료구조로 되돌리는(디코딩) 과정을 하나씩 따라가 볼게요.</p>
<blockquote>
<p>출처: <a href="https://go.dev/doc/tutorial/json">Go 공식 문서</a></p>
</blockquote>
<h2 id="인코딩-encoding">인코딩 (Encoding)</h2>
<p>JSON 데이터를 만들 때는 <code>Marshal</code> 함수를 씁니다.</p>
<pre><code class="language-go">func Marshal(in any, opts ...Options) (out []byte, err error)
</code></pre>
<p>아래와 같은 Go 자료구조 <code>Message</code>가 있다고 해 볼게요.</p>
<pre><code class="language-go">type Message struct {
    Name string
    Body string
    Time time.Time
}
</code></pre>
<p><code>Message</code>의 인스턴스를 하나 만들고,</p>
<pre><code class="language-go">m := Message{&quot;Alice&quot;, &quot;Hello&quot;, time.Date(2011, 1, 25, 0, 0, 0, 0, time.UTC)}
</code></pre>
<p><code>json.Marshal</code>로 <code>m</code>의 JSON 버전을 만들 수 있습니다.</p>
<pre><code class="language-go">b, err := json.Marshal(m)
</code></pre>
<p>문제가 없다면 <code>err</code>는 <code>nil</code>이고, <code>b</code>는 아래 JSON 데이터를 담은 <code>[]byte</code>가 될 거예요.</p>
<pre><code class="language-go">b == []byte(`{&quot;Name&quot;:&quot;Alice&quot;,&quot;Body&quot;:&quot;Hello&quot;,&quot;Time&quot;:&quot;2011-01-25T00:00:00Z&quot;}`)
</code></pre>
<p>유효한 JSON으로 표현할 수 있는 자료구조만 인코딩된다는 점을 기억해 두세요.</p>
<ul>
<li>포인터는 그 포인터가 가리키는 값으로 인코딩됩니다(포인터가 <code>nil</code>이면 <code>null</code>로).</li>
<li>JSON 객체는 키로 문자열만 지원합니다. 그래서 Go 맵을 인코딩하려면 키가 JSON 문자열로 인코딩되어야 해요. 예를 들어 <code>map[string]T</code> 같은 형태여야 합니다(<code>T</code>는 <code>json</code> 패키지가 지원하는 Go 타입).</li>
<li><code>channel</code>, <code>complex</code>, <code>function</code> 타입은 인코딩할 수 없어요.</li>
<li>순환 자료구조(cyclic data structure)는 지원되지 않습니다.</li>
</ul>
<p><code>Marshal</code>은 전체 인코딩 의미를 문서로 정리해 두고 있으니 필요할 때 참고하면 됩니다.</p>
<p><code>json</code> 패키지는 구조체 타입의 <strong>exported 필드</strong>(대문자로 시작하는 필드)에만 접근합니다. 그래서 JSON 출력에는 구조체의 exported 필드만 나타나요.</p>
<h2 id="디코딩-decoding">디코딩 (Decoding)</h2>
<p>JSON 데이터를 Go 자료구조로 되돌리는 데는 <code>Unmarshal</code> 함수를 씁니다.</p>
<pre><code class="language-go">func Unmarshal(in []byte, out any, opts ...Options) (err error)
</code></pre>
<p>먼저 디코딩된 데이터가 저장될 자리를 만들어야 합니다.</p>
<pre><code class="language-go">var m Message
</code></pre>
<p>그리고 <code>json.Unmarshal</code>을 호출하는데, JSON 데이터가 담긴 <code>[]byte</code>와 <code>m</code>의 포인터를 넘겨줍니다.</p>
<pre><code class="language-go">err := json.Unmarshal(b, &amp;m)
</code></pre>
<p>만약 <code>b</code>가 <code>m</code>에 들어맞는 유효한 JSON이라면, 호출 이후 <code>err</code>는 <code>nil</code>이고 <code>b</code>의 데이터가 구조체 <code>m</code>에 저장됩니다. 정확히 아래와 같은 대입을 한 것과 같다고 보면 돼요.</p>
<pre><code class="language-go">m = Message{
    Name: &quot;Alice&quot;,
    Body: &quot;Hello&quot;,
    Time: time.Date(2011, 1, 25, 0, 0, 0, 0, time.UTC),
}
</code></pre>
<p>그럼 <code>Unmarshal</code>은 디코딩된 데이터를 어느 필드에 저장할지 어떻게 알까요? 특정 JSON 키 <code>&quot;Foo&quot;</code>에 대해 <code>Unmarshal</code>은 목적지 구조체의 필드를 다음 우선순위로 찾아봅니다.</p>
<ul>
<li><code>json:&quot;Foo&quot;</code> 태그가 달린 exported 필드 (Go 명세의 struct tag에 대해 더 알아보고 싶다면 그쪽을 참고하세요).</li>
<li>이름이 <code>Foo</code>인 exported 필드.</li>
</ul>
<p>JSON 데이터의 구조가 Go 타입과 정확히 일치하지 않으면 어떻게 될까요?</p>
<pre><code class="language-go">b := []byte(`{&quot;Name&quot;:&quot;Bob&quot;,&quot;Food&quot;:&quot;Pickle&quot;}`)
var m Message
err := json.Unmarshal(b, &amp;m)
</code></pre>
<p><code>Unmarshal</code>은 목적지 타입에서 찾을 수 있는 필드만 디코딩합니다. 이 경우 <code>m</code>의 <code>Name</code> 필드만 채워지고 <code>Food</code> 필드는 무시돼요. 이 동작은 큰 JSON 덩어리에서 특정 필드 몇 개만 골라내고 싶을 때 특히 유용합니다. 그리고 목적지 구조체의 unexported 필드는 <code>Unmarshal</code>의 영향을 받지 않는다는 뜻이기도 하죠.</p>
<p>그렇다면, JSON 데이터의 구조를 <strong>미리 알 수 없는</strong> 경우에는 어떻게 할까요?</p>
<h2 id="any로-제네릭하게-다루기"><code>any</code>로 제네릭하게 다루기</h2>
<p><code>any</code> 타입은 어떤 Go 타입이든 나타냅니다. <code>any</code>는 <code>interface{}</code>(빈 인터페이스)로 정의되는데, 메서드가 0개인 인터페이스를 말해요. 모든 Go 타입은 메서드를 0개 이상 구현하므로 빈 인터페이스를 충족합니다. 그래서 <code>any</code>는 일반적인 컨테이너 타입 역할을 합니다.</p>
<pre><code class="language-go">var a any
a = &quot;a string&quot;
a = 2011
a = 2.777
</code></pre>
<p>타입 단언(type assertion)으로 기저의 구체 타입에 접근할 수 있어요.</p>
<pre><code class="language-go">r := a.(float64)
fmt.Println(&quot;the circle's area&quot;, math.Pi*r*r)
</code></pre>
<p>아니면 기저 타입을 모를 때는 타입 스위치(type switch)로 그 타입을 알아냅니다.</p>
<pre><code class="language-go">switch v := a.(type) {
case int:
    fmt.Println(&quot;twice a is&quot;, v*2)
case float64:
    fmt.Println(&quot;the reciprocal of a is&quot;, 1/v)
case string:
    h := len(v) / 2
    fmt.Println(&quot;a swapped by halves is&quot;, v[h:]+v[:h])
default:
    // a isn't one of the types above
}
</code></pre>
<p><code>json</code> 패키지는 <code>map[string]any</code>와 <code>[]any</code> 값을 써서 임의의 JSON 객체와 배열을 저장합니다. 유효한 JSON 덩어리라면 뭐든 그냥 <code>any</code> 값으로 <code>Unmarshal</code>해 버리죠. 이때 기본으로 대응되는 구체 Go 타입은 다음과 같습니다.</p>
<ul>
<li>JSON boolean → <code>bool</code></li>
<li>JSON number → <code>float64</code></li>
<li>JSON string → <code>string</code></li>
<li>JSON null → <code>nil</code></li>
</ul>
<h2 id="임의의-데이터-디코딩하기-decoding-arbitrary-data">임의의 데이터 디코딩하기 (Decoding arbitrary data)</h2>
<p>변수 <code>b</code>에 담긴 이런 JSON 데이터를 생각해 봅시다.</p>
<pre><code class="language-go">b := []byte(`{&quot;Name&quot;:&quot;Wednesday&quot;,&quot;Age&quot;:6,&quot;Parents&quot;:[&quot;Gomez&quot;,&quot;Morticia&quot;]}`)
</code></pre>
<p>이 데이터의 구조를 모르더라도 <code>Unmarshal</code>로 <code>any</code> 값에 디코딩할 수 있어요.</p>
<pre><code class="language-go">var f any
err := json.Unmarshal(b, &amp;f)
</code></pre>
<p>이 시점에 <code>f</code>의 Go 값은 키가 문자열이고 값이 다시 <code>any</code>로 저장된 맵이 됩니다.</p>
<pre><code class="language-go">f = map[string]any{
    &quot;Name&quot;: &quot;Wednesday&quot;,
    &quot;Age&quot;:  6,
    &quot;Parents&quot;: []any{
        &quot;Gomez&quot;,
        &quot;Morticia&quot;,
    },
}
</code></pre>
<p>이 데이터에 접근하려면 타입 단언으로 <code>f</code>의 기저 타입인 <code>map[string]any</code>를 꺼내면 됩니다.</p>
<pre><code class="language-go">m := f.(map[string]any)
</code></pre>
<p>그 다음엔 <code>range</code> 문으로 맵을 순회하고 타입 스위치로 값들에 각자의 구체 타입으로 접근할 수 있어요.</p>
<pre><code class="language-go">for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, &quot;is string&quot;, vv)
    case float64:
        fmt.Println(k, &quot;is float64&quot;, vv)
    case []any:
        fmt.Println(k, &quot;is an array:&quot;)
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, &quot;is of a type I don't know how to handle&quot;)
    }
}
</code></pre>
<p>이렇게 하면 타입 안전성의 이점을 누리면서도 알 수 없는 JSON 데이터를 다룰 수 있습니다.</p>
<h2 id="참조-타입-reference-types">참조 타입 (Reference Types)</h2>
<p>앞 예제의 데이터를 담을 Go 타입을 하나 정의해 볼게요.</p>
<pre><code class="language-go">type FamilyMember struct {
    Name    string
    Age     int
    Parents []string
}

var m FamilyMember
err := json.Unmarshal(b, &amp;m)
</code></pre>
<p>그 데이터를 <code>FamilyMember</code> 값으로 <code>Unmarshal</code>하면 예상대로 되지만, 자세히 들여다보면 놀라운 일이 벌어집니다. <code>var</code> 문으로 <code>FamilyMember</code> 구조체를 할당하고 그 값의 포인터를 <code>Unmarshal</code>에 넘겼는데, 그 시점에 <code>Parents</code> 필드는 <code>nil</code> 슬라이스였죠. 그런데 <code>Parents</code> 필드를 채우기 위해 <code>Unmarshal</code>이 뒤에서 새 슬라이스를 할당해 버립니다. 이건 <code>Unmarshal</code>이 지원하는 참조 타입(포인터, 슬라이스, 맵)에서 전형적으로 나타나는 동작이에요.</p>
<p>이런 자료구조로 <code>Unmarshal</code>하는 경우도 생각해 봅시다.</p>
<pre><code class="language-go">type Foo struct {
    Bar *Bar
}
</code></pre>
<p>JSON 객체에 <code>Bar</code> 필드가 있다면 <code>Unmarshal</code>은 새 <code>Bar</code>를 할당해서 채웁니다. 없다면 <code>Bar</code>는 <code>nil</code> 포인터로 남겨져요.</p>
<p>여기서 유용한 패턴이 나옵니다. 서로 다른 몇 가지 메시지 타입을 받는 애플리케이션이 있다면, 이렇게 '수신용' 구조체를 정의해 둘 수 있어요.</p>
<pre><code class="language-go">type IncomingMessage struct {
    Cmd *Command
    Msg *Message
}
</code></pre>
<p>그리고 보내는 쪽은 전달하고 싶은 메시지 종류에 따라 최상위 JSON 객체의 <code>Cmd</code> 필드나 <code>Msg</code> 필드(또는 둘 다)를 채우면 됩니다. <code>Unmarshal</code>은 JSON 데이터를 <code>IncomingMessage</code> 구조체로 디코딩할 때 JSON 데이터에 실제로 존재하는 자료구조만 할당해요. 그래서 어떤 메시지를 처리할지 알기 위해 프로그래머는 그냥 <code>Cmd</code>나 <code>Msg</code>가 <code>nil</code>이 아닌지만 확인하면 됩니다.</p>
<h2 id="스트리밍-marshal과-unmarshal">스트리밍 <code>Marshal</code>과 <code>Unmarshal</code></h2>
<p><code>io.Reader</code>와 <code>io.Writer</code> 인터페이스는 Go 곳곳에서 쓰입니다. HTTP 연결, WebSocket, 파일 같은 리소스에 스트리밍 방식으로 접근하게 해 주죠. <code>MarshalWrite</code>와 <code>UnmarshalRead</code> 함수를 쓰면 중간에 메시지 전체를 담은 <code>[]byte</code>를 만들지 않고도 이 스트림들에 직접 마샬링/언마샬링할 수 있습니다.</p>
<pre><code class="language-go">func MarshalWrite(out io.Writer, in any, opts ...Options) (err error)
func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error)
</code></pre>
<p>예를 들어 마샬 출력을 표준 출력에 직접 쓸 수 있어요.</p>
<pre><code class="language-go">err := json.MarshalWrite(os.Stdout, m)
</code></pre>
<h2 id="커스텀-marshal과-unmarshal">커스텀 <code>Marshal</code>과 <code>Unmarshal</code></h2>
<p>때로는 기본 마샬 동작이 우리 타입에는 맞지 않을 때가 있습니다. 소프트웨어 버전을 나타내는 타입을 하나 가정해 볼게요.</p>
<pre><code class="language-go">type Version struct {
    Major, Minor, Patch int64
}

v := Version{1, 2, 3}
b, err := json.Marshal(s)
</code></pre>
<p>구조체 정의에 따라 이건 <code>{&quot;Major&quot;:1,&quot;Minor&quot;:2,&quot;Patch&quot;:3}</code>으로 마샬됩니다. 하지만 JSON을 소비하는 쪽에겐 <code>&quot;1.2.3&quot;</code> 같은 버전 문자열이 훨씬 나은 표현일 겁니다.</p>
<p><code>MarshalerTo</code> 인터페이스를 구현하면 커스텀 JSON 표현을 제공할 수 있어요. <code>UnmarshalerFrom</code>은 그 커스텀 JSON 표현의 언마샬링을 담당합니다.</p>
<pre><code class="language-go">type MarshalerTo interface {
    MarshalJSONTo(*jsontext.Encoder) error
}

type UnmarshalerFrom interface {
    UnmarshalJSONFrom(*jsontext.Decoder) error
}
</code></pre>
<p>이 메서드들을 구현해서 문자열 표현으로, 그리고 문자열 표현에서 변환할 수 있습니다.</p>
<pre><code class="language-go">func (v Version) MarshalJSONTo(enc *jsontext.Encoder) error {
    return json.MarshalEncode(enc, fmt.Sprintf(&quot;%d.%d.%d&quot;, v.Major, v.Minor, v.Patch))
}
</code></pre>
<p>커스텀 마샬링을 적용하면 <code>Version{1, 2, 3}</code> 값은 이제 <code>&quot;1.2.3&quot;</code>으로 마샬됩니다.</p>
<pre><code class="language-go">func (v *Version) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
    if k := dec.PeekKind(); k != jsontext.KindString {
        // Value must be a string.
        return &amp;json.SemanticError{JSONKind: k}
    }

    var s string
    if err := json.UnmarshalDecode(dec, &amp;s); err != nil {
        return err
    }

    _, err := fmt.Sscanf(s, &quot;%d.%d.%d&quot;, &amp;v.Major, &amp;v.Minor, &amp;v.Patch)
    return err
}
</code></pre>
<p>커스텀 언마샬링을 적용하면 <code>&quot;1.2.3&quot;</code>이 이제 <code>Version{1, 2, 3}</code>으로 언마샬됩니다. 이 단순한 예제는 정확한 <code>&quot;major.minor.patch&quot;</code> 형식만 요구하지만, 더 복잡한 <code>UnmarshalJSONFrom</code>은 minor와 patch 버전을 선택 사항으로 만드는 식으로 유연성을 더할 수도 있어요.</p>
<h2 id="참고-자료-references">참고 자료 (References)</h2>
<p>더 자세한 내용은 <code>encoding/json/v2</code>와 <code>encoding/json/jsontext</code> 패키지 문서를 확인하세요.</p>
<h2 id="더-알아보기-learn-more">더 알아보기 (Learn more)</h2>
<ul>
<li>Go에서 JSON을 다루는 또 다른 입문 자료가 궁금하다면 Go 블로그의 &quot;JSON and Go&quot; 글을 찾아보세요.</li>
<li><code>encoding/json</code>(구버전)과의 차이에 관심이 있다면 <code>encoding/json/v2</code> 패키지 문서를 확인하세요.</li>
</ul>
</div>
      </article>

      <!-- ═══ 오른쪽: TOC (H2/H3 목차, 접기/펼치기) ═══ -->
      <aside class="wiki-toc" data-toc
             style="position:sticky; top:calc(var(--header-h) + 24px); max-height:calc(100vh - var(--header-h) - 48px); overflow:auto; padding:8px 0; border-left:1px solid var(--ds-line); padding-left:16px;">
        <button type="button" class="wiki-toc__toggle" aria-expanded="true" aria-controls="wiki-toc-nav">
          <span class="wiki-toc__label">목차</span>
          <span class="wiki-toc__chevron">▾</span>
        </button>
        <nav id="wiki-toc-nav" class="wiki-toc__nav" aria-label="페이지 목차">
          <a href="#json-다루기-튜토리얼" style="padding-left:0px">JSON 다루기 튜토리얼</a><a href="#인코딩-encoding" style="padding-left:0px">인코딩 (Encoding)</a><a href="#디코딩-decoding" style="padding-left:0px">디코딩 (Decoding)</a><a href="#any로-제네릭하게-다루기" style="padding-left:0px">any로 제네릭하게 다루기</a><a href="#임의의-데이터-디코딩하기-decoding-arbitrary-data" style="padding-left:0px">임의의 데이터 디코딩하기 (Decoding arbitrary data)</a><a href="#참조-타입-reference-types" style="padding-left:0px">참조 타입 (Reference Types)</a><a href="#스트리밍-marshal과-unmarshal" style="padding-left:0px">스트리밍 Marshal과 Unmarshal</a><a href="#커스텀-marshal과-unmarshal" style="padding-left:0px">커스텀 Marshal과 Unmarshal</a><a href="#참고-자료-references" style="padding-left:0px">참고 자료 (References)</a><a href="#더-알아보기-learn-more" style="padding-left:0px">더 알아보기 (Learn more)</a>
        </nav>
      </aside>
    </div>
  </main>

  <!-- ═══ scope tree 접기/펼치기 인터랙션 ═══ -->
  <script>
  (function () {
    // 페이지 이동 시 항상 최상단(제목/목차)에서 시작 — 모바일에서 트리가 하단에 있어도 뷰포트를 하단에 남기지 않음
    if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; }
    window.addEventListener('load', function () { window.scrollTo(0, 0); });

    document.querySelectorAll('[data-wtree-toggle]').forEach(function (btn) {
      btn.addEventListener('click', function (e) {
        e.preventDefault();
        e.stopPropagation();
        var li = btn.closest('.wiki-tree__node');
        if (!li) return;
        var sub = li.querySelector(':scope > .wiki-tree');
        if (!sub) return;
        var lazyPath = sub.getAttribute('data-wtree-lazy');
        if (lazyPath && sub.getAttribute('data-wtree-loaded') !== '1') {
          // 첫 펼침: 하위 트리를 lazy 프래그먼트로 로드 후 펼침
          fetch('/wiki/tree/' + lazyPath.replace(/^\/+/, ''))
            .then(function (r) { return r.text(); })
            .then(function (html) {
              sub.innerHTML = html;
              sub.setAttribute('data-wtree-loaded', '1');
              sub.removeAttribute('data-wtree-lazy');
              wireToggles(sub);
              openSub(sub, btn);
            })
            .catch(function () { openSub(sub, btn); });
          return;
        }
        openSub(sub, btn);
      });
    });

    function openSub(sub, btn) {
      var collapsed = sub.classList.toggle('is-collapsed');
      sub.hidden = collapsed;
      btn.textContent = collapsed ? '▸' : '▾';
      btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
    }

    // lazy 프래그먼트로 삽입된 하위 노드들의 토글도 이벤트 연결
    function wireToggles(scope) {
      scope.querySelectorAll('[data-wtree-toggle]').forEach(function (btn) {
        if (btn._wtreeBound) return;
        btn._wtreeBound = true;
        btn.addEventListener('click', function (e) {
          e.preventDefault();
          e.stopPropagation();
          var li = btn.closest('.wiki-tree__node');
          if (!li) return;
          var sub = li.querySelector(':scope > .wiki-tree');
          if (!sub) return;
          var lazyPath = sub.getAttribute('data-wtree-lazy');
          if (lazyPath && sub.getAttribute('data-wtree-loaded') !== '1') {
            fetch('/wiki/tree/' + lazyPath.replace(/^\/+/, ''))
              .then(function (r) { return r.text(); })
              .then(function (html) {
                sub.innerHTML = html;
                sub.setAttribute('data-wtree-loaded', '1');
                sub.removeAttribute('data-wtree-lazy');
                wireToggles(sub);
                openSub(sub, btn);
              })
              .catch(function () { openSub(sub, btn); });
            return;
          }
          openSub(sub, btn);
        });
      });
    }

    // TOC 접기/펼치기 토글
    var toc = document.querySelector('[data-toc]');
    if (toc) {
      var toggleBtn = toc.querySelector('.wiki-toc__toggle');
      var nav = toc.querySelector('.wiki-toc__nav');
      var chevron = toc.querySelector('.wiki-toc__chevron');
      if (toggleBtn && nav) {
        toggleBtn.addEventListener('click', function () {
          var collapsed = toc.classList.toggle('is-collapsed');
          toggleBtn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
          if (chevron) chevron.textContent = collapsed ? '▸' : '▾';
        });
      }
    }
    // 페이지 진입 시 활성 노드로 스크롤 노출
    var active = document.querySelector('.wiki-tree__node.is-active');
    if (active && active.scrollIntoView) {
      try { active.scrollIntoView({ block: 'nearest' }); } catch (e) {}
    }
  })();
  </script>

  <style>
    /* scope tree 행 스타일: 활성 강조 + 들여쓰기 토글 정렬 */
    .wiki-tree__row { display:flex; align-items:center; gap:6px; border-radius:var(--r-xs); }
    .wiki-tree__row:hover { background:var(--ds-bg-subtle); }
    .wiki-tree__toggle {
      flex:0 0 auto; width:16px; height:22px; padding:0; border:none; background:none;
      cursor:pointer; color:var(--ds-text-3); font-size:10px; line-height:1;
      display:flex; align-items:center; justify-content:center;
    }
    .wiki-tree__dot { flex:0 0 auto; width:16px; height:1px; }
    .wiki-tree__link {
      flex:1 1 auto; min-width:0; padding:6px 6px; border-radius:var(--r-xs);
      font-size:13px; color:var(--ds-text-2); text-decoration:none; line-height:1.35; overflow:hidden; text-overflow:ellipsis;
    }
    .wiki-tree__node.is-active > .wiki-tree__row { background:var(--ds-text); }
    .wiki-tree__node.is-active > .wiki-tree__row .wiki-tree__link { color:var(--ds-bg-card); font-weight:700; }
    .wiki-tree.is-collapsed { display:none; }
    /* TOC 접기/펼치기 토글 버튼 */
    .wiki-toc__toggle {
      display:flex; align-items:center; justify-content:space-between; width:100%;
      padding:4px 6px; margin-bottom:6px; border:none; background:none; cursor:pointer;
      font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
      color:var(--ds-text-2); border-radius:var(--r-xs); text-align:left;
    }
    .wiki-toc__toggle:hover { color:var(--ds-text); background:var(--ds-bg-subtle); }
    .wiki-toc__nav { display:flex; flex-direction:column; gap:2px; }
    .wiki-toc__chevron { font-size:10px; color:var(--ds-text-3); transition:transform .15s var(--ease-out); }
    .wiki-toc.is-collapsed .wiki-toc__chevron { transform:rotate(-90deg); }
    .wiki-toc.is-collapsed .wiki-toc__nav { display:none; }
  </style>

  <html>
<footer class="site-footer--compact" style="background:var(--ds-bg-strong); color:var(--ds-text-on-dark); margin-top:80px;">
  <div class="site-footer--compact__inner">
    <!-- 1줄 블록: 좌측 브랜드 + 우측 링크 가로 나열 -->
    <div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:20px 32px;">
      <div style="display:flex; align-items:center; gap:16px; flex-wrap:wrap;">
        <img src="/assets/logo-white.png" alt="DataSketchers" style="height:32px; width:auto;" />
        <span style="font-size:13px; color:#b0b0b2; max-width:320px;">AI·데이터·디자인으로 기업의 디지털 전환을 지원합니다. 서울시립대학교 캠퍼스타운 창업기업</span>
      </div>
      <nav style="display:flex; flex-wrap:wrap; gap:8px 24px; font-size:13px;" aria-label="푸터 링크">
        <a href="/about" style="color:#e5e5e6; text-decoration:none;">회사소개</a>
        <a href="/services" style="color:#e5e5e6; text-decoration:none;">사업영역</a>
        <a href="/products" style="color:#e5e5e6; text-decoration:none;">D-SKET</a>
        <a href="/clients" style="color:#e5e5e6; text-decoration:none;">고객사</a>
        <a href="/blog" style="color:#e5e5e6; text-decoration:none;">블로그</a>
        <a href="/" style="color:#e5e5e6; text-decoration:none;">위키</a>
        <a href="/press" style="color:#e5e5e6; text-decoration:none;">보도자료</a>
        <a href="/esg" style="color:#e5e5e6; text-decoration:none;">ESG</a>
        <a href="/contact" style="color:#e5e5e6; text-decoration:none;">문의</a>
      </nav>
    </div>
    <div style="margin-top:28px; padding-top:16px; border-top:1px solid rgba(255,255,255,0.12); display:flex; justify-content:space-between; flex-wrap:wrap; gap:12px; font-size:13px; color:#8a8a8d;">
      <span>© 데이터스케쳐스. All rights reserved.</span>
      <span>사업자등록번호 349-81-03411</span>
    </div>
  </div>
</footer>
</html>
</body>
</html>
