1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Compositeパターンで柔軟なJSONデータ処理:条件付き要素抽出の実装

Last updated at Posted at 2024-10-09

はじめに

ソフトウェア開発において、JSONデータの処理は避けて通れない課題です。特に、深くネストされた複雑なJSONデータを扱う場合、コードが煩雑になりがちです。本記事では、Compositeパターンを活用して、複雑なJSONデータから特定の条件に合致する要素を抽出し、新しい構造を生成する柔軟な処理システムを実装します。

なぜCompositeパターンを使うのか

構造に関するデザインパターン

image.png

  1. 統一的なインターフェース: JSON要素(オブジェクト、配列、プリミティブ値)を同じインターフェースで扱えます。
  2. 再帰的な処理: ネストされた構造を簡単に走査できます。
  3. 拡張性: 新しい処理ルールや要素タイプの追加が容易です。

実装例:条件付きJSONデータ抽出器

image.png

以下の実装では、JSONデータを解析し、特定の条件に合致する要素のみを抽出して新しい構造を生成します。

import json
from abc import ABC, abstractmethod
from typing import Any, Dict, List

class JSONComponent(ABC):
    @abstractmethod
    def process(self, condition: callable) -> Any:
        pass

class JSONPrimitive(JSONComponent):
    def __init__(self, value: Any):
        self.value = value

    def process(self, condition: callable) -> Any:
        return self.value

class JSONObject(JSONComponent):
    def __init__(self, data: Dict):
        self.data = {k: self._wrap(v) for k, v in data.items()}

    def _wrap(self, item: Any) -> JSONComponent:
        if isinstance(item, dict):
            return JSONObject(item)
        elif isinstance(item, list):
            return JSONArray(item)
        else:
            return JSONPrimitive(item)

    def process(self, condition: callable) -> Dict:
        result = {}
        for key, value in self.data.items():
            processed = value.process(condition)
            if isinstance(processed, (dict, list)) or (isinstance(processed, (str, int, float, bool)) and condition(processed)):
                result[key] = processed
        return result if result else None

class JSONArray(JSONComponent):
    def __init__(self, data: List):
        self.data = [self._wrap(item) for item in data]

    def _wrap(self, item: Any) -> JSONComponent:
        if isinstance(item, dict):
            return JSONObject(item)
        elif isinstance(item, list):
            return JSONArray(item)
        else:
            return JSONPrimitive(item)

    def process(self, condition: callable) -> List:
        result = []
        for item in self.data:
            processed = item.process(condition)
            if isinstance(processed, (dict, list)) or (isinstance(processed, (str, int, float, bool)) and condition(processed)):
                result.append(processed)
        return result if result else None

class JSONProcessor:
    @staticmethod
    def extract(json_data: Dict, condition: callable) -> Dict:
        root = JSONObject(json_data)
        return root.process(condition)

# 使用例
if __name__ == "__main__":
    # サンプルJSONデータ
    sample_data = {
        "name": "John Doe",
        "age": 30,
        "contacts": [
            {"type": "email", "value": "john@example.com"},
            {"type": "phone", "value": "1234567890"}
        ],
        "address": {
            "street": "123 Main St",
            "city": "Anytown",
            "country": "USA",
            "zip": "12345"
        },
        "interests": ["programming", "reading", "traveling"]
    }

    # 条件: 文字列で、長さが5以上の要素のみを抽出
    condition = lambda x: isinstance(x, str) and len(x) >= 5

    result = JSONProcessor.extract(sample_data, condition)
    print("文字列で長さが5以上の要素:")
    print(json.dumps(result, indent=2))

    # 数値を含む要素のみを抽出
    numeric_condition = lambda x: isinstance(x, (int, float)) or (isinstance(x, str) and any(char.isdigit() for char in x))
    
    numeric_result = JSONProcessor.extract(sample_data, numeric_condition)
    print("\n数値を含む要素:")
    print(json.dumps(numeric_result, indent=2))

実装の解説

JSONComponent

抽象基底クラスで、すべてのJSON要素の共通インターフェースを定義します。

JSONPrimitive

文字列、数値、真偽値などのプリミティブ値を表します。processメソッドは値をそのまま返します。

JSONObject

キーと値のペアを持つオブジェクトを表します。processメソッドは、各子要素に対して再帰的に処理を適用し、条件に合致する要素のみを含む新しいオブジェクトを生成します。

JSONArray

値のリストを表します。processメソッドは、各要素に対して再帰的に処理を適用し、条件に合致する要素のみを含む新しいリストを生成します。

JSONProcessor

クライアントコードからの使用を簡略化するためのファサードとして機能します。

実行結果

この実装を実行すると、以下のような出力が得られます:

  1. 文字列で長さが5以上の要素を抽出した結果:
{
  "name": "John Doe",
  "contacts": [
    {
      "value": "john@example.com"
    },
    {
      "value": "1234567890"
    }
  ],
  "address": {
    "street": "123 Main St",
    "city": "Anytown"
  },
  "interests": [
    "programming",
    "reading",
    "traveling"
  ]
}
  1. 数値を含む要素を抽出した結果:
{
  "age": 30,
  "contacts": [
    {
      "value": "1234567890"
    }
  ],
  "address": {
    "street": "123 Main St",
    "zip": "12345"
  }
}

この実装のメリット

  1. 柔軟性: 異なる条件を簡単に適用できます。
  2. 再利用性: 様々なJSONデータ構造に対して同じ処理を適用できます。
  3. 拡張性: 新しい種類の要素や処理ルールを追加しやすい構造です。
  4. 構造の保持: JSON の階層構造を維持したまま処理できます。

使用シーン

  • APIレスポンスのフィルタリングと正規化
  • 大規模なJSONデータからの特定情報の抽出
  • データ変換処理のパイプライン構築
  • 複雑なJSONデータの条件付き要素抽出

まとめ

image.png

Compositeパターンを活用することで、複雑なJSONデータ処理を柔軟かつ効率的に実装できることがわかりました。この手法は、データの正規化、フィルタリング、変換など、多くの実際のユースケースで応用可能です。

実装を通じて、Compositeパターンがデータ処理においても強力なツールとなることが確認できました。読者の皆さんも、ぜひこの実装を基に、自身のプロジェクトでの活用を検討してみてください。JSONデータ処理の課題に直面した際、このCompositeパターンを用いたアプローチが有効な解決策となるでしょう。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?