본문 바로가기
스마트폰 (Mobile)

Jinja2로 JSON 데이터 처리 및 필터링·변환·집계 다루기 고급 활용

by 날으는물고기 2025. 9. 28.

Jinja2로 JSON 데이터 처리 및 필터링·변환·집계 다루기 고급 활용

728x90

1. 기본 개념

1.1 Jinja2에서 JSON 처리의 핵심 원리

  • Jinja2는 템플릿 엔진으로, JSON 데이터를 문자열로 변환 후 from_json 필터로 파싱
  • 직접적인 객체 조작보다는 문자열 기반 JSON 생성이 더 안정적
  • 홈어시스턴트 등 제한된 환경에서는 일부 내장 함수 사용 불가

1.2 주요 필터 및 함수

{{ data | tojson }}          # 객체를 JSON 문자열로 변환
{{ json_str | from_json }}   # JSON 문자열을 객체로 파싱
{{ array | selectattr() }}   # 배열에서 특정 속성 기준 필터링
{{ array | rejectattr() }}   # 배열에서 특정 속성 기준 제외
{{ array | map() }}          # 배열 각 요소에 함수 적용

2. 데이터 구조별 처리 방법

2.1 단순 객체 필터링

예시 데이터

{
  "name": "김철수",
  "age": 30,
  "email": "kim@example.com",
  "phone": null,
  "address": "",
  "active": true
}

null과 빈값 제거

{% set json_str %}{
  {% for k, v in user_data.items() if v is not none and v != '' %}
    {% if not loop.first %},{% endif %}
    "{{ k }}": "{{ v }}"
  {% endfor %}
}{% endset %}
{{ json_str | from_json }}

결과

{
  "name": "김철수",
  "age": "30",
  "email": "kim@example.com",
  "active": "true"
}

2.2 배열 데이터 필터링

예시 데이터

[
  {
    "id": 1,
    "name": "상품A",
    "price": 10000,
    "category": "전자제품",
    "stock": null,
    "description": ""
  },
  {
    "id": 2,
    "name": "상품B",
    "price": null,
    "category": "의류",
    "stock": 50,
    "description": "좋은 상품"
  }
]

배열 내 객체들의 null/빈값 제거

{% set json_str %}[
  {% for item in products %}
    {% if not loop.first %},{% endif %}
    {
      {% for k, v in item.items() if v is not none and v != '' %}
        {% if not loop.first %},{% endif %}
        "{{ k }}": "{{ v }}"
      {% endfor %}
    }
  {% endfor %}
]{% endset %}
{{ json_str | from_json }}

2.3 중첩 구조 처리

예시 데이터

{
  "company": "테크코프",
  "departments": [
    {
      "name": "개발팀",
      "manager": "김팀장",
      "employees": [
        {"name": "이개발", "role": "시니어", "salary": null},
        {"name": "박주니어", "role": null, "salary": 3000}
      ],
      "budget": null
    }
  ],
  "founded": 2020,
  "revenue": null
}

중첩 구조 정리

{% set json_str %}{
  {% for k, v in company_data.items() if v is not none %}
    {% if not loop.first %},{% endif %}
    "{{ k }}": 
    {% if k == 'departments' %}
      [
        {% for dept in v %}
          {% if not loop.first %},{% endif %}
          {
            {% for dk, dv in dept.items() if dv is not none %}
              {% if not loop.first %},{% endif %}
              "{{ dk }}": 
              {% if dk == 'employees' %}
                [
                  {% for emp in dv %}
                    {% if not loop.first %},{% endif %}
                    {
                      {% for ek, ev in emp.items() if ev is not none %}
                        {% if not loop.first %},{% endif %}
                        "{{ ek }}": "{{ ev }}"
                      {% endfor %}
                    }
                  {% endfor %}
                ]
              {% else %}
                "{{ dv }}"
              {% endif %}
            {% endfor %}
          }
        {% endfor %}
      ]
    {% else %}
      "{{ v }}"
    {% endif %}
  {% endfor %}
}{% endset %}
{{ json_str | from_json }}

3. 고급 필터링 기법

3.1 조건부 필터링

특정 조건을 만족하는 데이터만 포함

{% set json_str %}[
  {% for item in products if item.price is not none and item.price > 5000 %}
    {% if not loop.first %},{% endif %}
    {
      {% for k, v in item.items() if v is not none %}
        {% if not loop.first %},{% endif %}
        "{{ k }}": "{{ v }}"
      {% endfor %}
    }
  {% endfor %}
]{% endset %}
{{ json_str | from_json }}

3.2 데이터 변환

데이터 타입 변환 및 계산

{% set json_str %}[
  {% for item in products %}
    {% if not loop.first %},{% endif %}
    {
      "id": {{ item.id }},
      "name": "{{ item.name }}",
      "price": {{ item.price if item.price else 0 }},
      "discounted_price": {{ (item.price * 0.9) | round(2) if item.price else 0 }},
      "category": "{{ item.category }}",
      "in_stock": {% if item.stock and item.stock > 0 %}true{% else %}false{% endif %}
    }
  {% endfor %}
]{% endset %}
{{ json_str | from_json }}

3.3 그룹화 및 집계

카테고리별 상품 그룹화

{% set categories = products | groupby('category') %}
{% set json_str %}{
  {% for category, items in categories %}
    {% if not loop.first %},{% endif %}
    "{{ category }}": {
      "count": {{ items | list | length }},
      "products": [
        {% for item in items %}
          {% if not loop.first %},{% endif %}
          {
            {% for k, v in item.items() if v is not none and k != 'category' %}
              {% if not loop.first %},{% endif %}
              "{{ k }}": "{{ v }}"
            {% endfor %}
          }
        {% endfor %}
      ]
    }
  {% endfor %}
}{% endset %}
{{ json_str | from_json }}
300x250

4. 실제 사용 사례

4.1 API 응답 데이터 정리

원본 API 응답

{
  "status": "success",
  "data": {
    "users": [
      {
        "id": 1,
        "username": "user1",
        "email": "user1@example.com",
        "profile": {
          "first_name": "홍",
          "last_name": "길동",
          "bio": null,
          "avatar": null,
          "created_at": "2024-01-01"
        },
        "settings": {
          "notifications": true,
          "privacy": null,
          "theme": "dark"
        }
      }
    ],
    "pagination": {
      "page": 1,
      "per_page": 10,
      "total": null,
      "has_more": false
    }
  },
  "message": null,
  "errors": null
}

정리된 응답 생성

{% set json_str %}{
  "status": "{{ api_response.status }}",
  "users": [
    {% for user in api_response.data.users %}
      {% if not loop.first %},{% endif %}
      {
        "id": {{ user.id }},
        "username": "{{ user.username }}",
        "email": "{{ user.email }}",
        "profile": {
          {% for k, v in user.profile.items() if v is not none %}
            {% if not loop.first %},{% endif %}
            "{{ k }}": "{{ v }}"
          {% endfor %}
        },
        "settings": {
          {% for k, v in user.settings.items() if v is not none %}
            {% if not loop.first %},{% endif %}
            "{{ k }}": {% if v is sameas true or v is sameas false %}{{ v | lower }}{% else %}"{{ v }}"{% endif %}
          {% endfor %}
        }
      }
    {% endfor %}
  ],
  "pagination": {
    {% for k, v in api_response.data.pagination.items() if v is not none %}
      {% if not loop.first %},{% endif %}
      "{{ k }}": {% if v is sameas true or v is sameas false %}{{ v | lower }}{% elif v is number %}{{ v }}{% else %}"{{ v }}"{% endif %}
    {% endfor %}
  }
}{% endset %}
{{ json_str | from_json }}

4.2 센서 데이터 집계

여러 센서 데이터 통합

{% set sensors = [
  {"name": "온도", "value": 23.5, "unit": "°C", "status": "normal", "error": null},
  {"name": "습도", "value": null, "unit": "%", "status": "error", "error": "센서 오류"},
  {"name": "압력", "value": 1013, "unit": "hPa", "status": "normal", "error": null}
] %}

{% set json_str %}{
  "timestamp": "{{ now().isoformat() }}",
  "summary": {
    "total_sensors": {{ sensors | length }},
    "active_sensors": {{ sensors | selectattr('value') | list | length }},
    "error_count": {{ sensors | selectattr('status', 'equalto', 'error') | list | length }}
  },
  "sensors": [
    {% for sensor in sensors %}
      {% if not loop.first %},{% endif %}
      {
        {% for k, v in sensor.items() if v is not none %}
          {% if not loop.first %},{% endif %}
          "{{ k }}": {% if v is number %}{{ v }}{% else %}"{{ v }}"{% endif %}
        {% endfor %}
      }
    {% endfor %}
  ]
}{% endset %}
{{ json_str | from_json }}

5. 성능 최적화 및 모범 사례

5.1 효율적인 필터링

{# 좋은 예: 조건을 먼저 체크 #}
{% for item in large_dataset if item.active and item.value %}
  ...
{% endfor %}

{# 나쁜 예: 모든 아이템을 순회 후 조건 체크 #}
{% for item in large_dataset %}
  {% if item.active and item.value %}
    ...
  {% endif %}
{% endfor %}

5.2 변수 활용으로 코드 간소화

{% set clean_fields = ['id', 'name', 'email', 'active'] %}
{% for item in data %}
  {
    {% for field in clean_fields if item[field] is defined and item[field] is not none %}
      "{{ field }}": "{{ item[field] }}"{% if not loop.last %},{% endif %}
    {% endfor %}
  }
{% endfor %}

5.3 에러 처리

{% set json_str %}{
  {% for item in data %}
    {% if item is mapping %}
      {% for k, v in item.items() if v is not none %}
        "{{ k }}": "{{ v | string | replace('"', '\\"') }}"
      {% endfor %}
    {% endif %}
  {% endfor %}
}{% endset %}

{# JSON 파싱 시 에러 처리 #}
{% set result = json_str | from_json %}
{% if result %}
  {{ result }}
{% else %}
  {"error": "JSON 파싱 실패"}
{% endif %}

6. 홈어시스턴트 특화 기법

6.1 센서 속성 정리

# configuration.yaml
sensor:
  - platform: template
    sensors:
      clean_weather_data:
        value_template: "OK"
        attribute_templates:
          processed_data: >
            {% set json_str %}{
              {% for k, v in state_attr('weather.home', 'forecast')[0].items() if v is not none %}
                {% if not loop.first %},{% endif %}
                "{{ k }}": "{{ v }}"
              {% endfor %}
            }{% endset %}
            {{ json_str | from_json }}

6.2 다중 엔티티 데이터 병합

{% set entities = ['sensor.temp1', 'sensor.temp2', 'sensor.humidity'] %}
{% set json_str %}[
  {% for entity_id in entities %}
    {% set entity = states[entity_id] %}
    {% if entity and entity.state != 'unavailable' %}
      {% if not loop.first %},{% endif %}
      {
        "entity_id": "{{ entity_id }}",
        "name": "{{ entity.attributes.friendly_name | default(entity_id) }}",
        "value": "{{ entity.state }}",
        "unit": "{{ entity.attributes.unit_of_measurement | default('') }}",
        "last_updated": "{{ entity.last_updated }}"
      }
    {% endif %}
  {% endfor %}
]{% endset %}
{{ json_str | from_json }}

이러한 방식들을 조합하여 복잡한 JSON 데이터도 효과적으로 처리할 수 있습니다. 핵심은 문자열 기반 JSON 생성과 적절한 조건 필터링, 그리고 최종적인 from_json 파싱입니다.

728x90
그리드형(광고전용)

댓글