JSON, XML (P)

構造化文書(Structred Document)

テキストの中にも、構造をもったものて色々ありますよね。 例えば、HTML は、テキストの構造を markup してあるわけで。

構造化文書によく使われる JSON と XML を扱っていきます。 Python については、一般的な dictで扱う方法と、class に変換する方法を扱います。

dict および class について学んでない場合は、先にそちらの確認を

JSON

JSON (JavaScript Object Notation) は、以下のようなテキストフォーマットです。

{   "name": "甲南大学",
    "established": 1951,
    "faculties":{
        "知能情報学部": {
            "established": 2008,
            "departments": ["知能情報学科"]
        },
        "理工学部": {
            "established": 2001,
            "departments": [ "物理学科", "生物学科", "機能分子化学科" ]
        },
	...
    }
}

構成要素は、以下のとおり。

  • オブジェクトと呼ばれる、名前のペア。( [ key0: val0, key1: val1, .. ] 相当)
  • 配列 と呼ばれる値のあるまり ( [ elem0, elem1, elem2, ..] 相当)
  • には、文字列、数値、true/false, null, オブジェクト、配列を取りうる。つまり、ネスト構造も可能。

基本的には、木構造です。JavaScript 言語のオブジェクト記法法をベースに策定されたそうです。

各種 Web Service のデータフォーマットなどにも使われています。

JSON と Python

今度は、Python で JSON を扱ってみましょう。

Python では、「キー」から「値」へのマッピング用データ構造として、組み込みデータ型の dict がありますので、そちらを使うのが一般的でしょう。

dict(one=1, two=2, three=3)

と書いても

{'one': 1, 'two': 2, 'three': 3}

と書いても、同じデータ構造になります。値として listdict を扱うこともできるので、もともと JSON みたいなデータ構造ですよね。

とはいえ、JSON 相当の文字列やファイルを、dict に変換するのは自分でやるのは大変です。ただ、jsonという JSON への encoder & decoder をつかえば簡単です。

先にファイルの読み書きをみましょうか。ファイル操作の際に文字コード指定をしています。それから、dump の際、対象 dict に日本語とか ascii 以外の文字が混じっている場合は、ensure_ascii=False という option 指定が必要です。ついでに indent option もつけてみました。

# load
with open("input.json", encoding='utf-8') as file_in:
    dct = json.load(file_in)
# dump    
with open("output.json", 'w+', encoding='utf-8') as file_out:
    json.dump(dct, file_out, indent=2, ensure_ascii=False)    

当該プログラムを実行すると、output.json というファイルができるはず。

今度は、文字列操作で、decode & encode してみます。

# dump
to_str = json.dumps(dct, indent=2, ensure_ascii=False)
# encode
dct2 = json.loads(to_str)

Python class への変換 (JSON)

dict は型がないので、JSON を扱うのには便利です。 一方で、established という要素にアクセスする際は、dict["established"] とか書くわけですが、「キー」の名前をミスタイプしてても、教えてくれません。こういう失敗で数時間失った経験がある人には笑い事ではありません。

ただ、実は Python にも class をつかってデータ構造定義ができるので、Java 同様クラスへの変換も試してみましょう。 pydanticという dict から python class に変換と Validation をしてくれるツールを使ってみます。

from pydantic import BaseModel  # 今回使うライブラリ
from typing import Dict, List   # Type Hints と呼ばれる型記述用モジュール


class Faculty(BaseModel):
    established: int
    departments: List[str]


class Univ(BaseModel):
    name: str
    established: int
    faculties: Dict[str, Faculty]

こんなクラスを定義するのですが、Type Hint と呼ばれる型情報を記述しています。 Univfaculties は、 str から Fuculty への Dict(ionary) になっているよって感じです。 一般の Dict とは違って、値の部分に好きなものがこない「はず」だよって宣言してます。

これで、

# dict から Univ への変換
univ = Univ(**dct)
# univ から dict への変換
dct2 = univ.dict()

で dict と class の相互変換ができました。 **dct ってのは、dct の key-value 組を関数呼び出しの引数として展開する記法で、興味のある人はこちらの解説でも見てください。

ちなみに、必ずしも、dict の全情報を class に変換している訳じゃないので、元に戻るわけではないですよ。

class になったメリットとしては、統合開発環境のコード補完機能とかが使えるようになります。 例えば、univ のメソッド呼びたいとき、univ. とピリオドまで入力したら、統合開発環境はメソッド名とか提示してくれて、ミスタイプの心配も減ります。

XML

XML (Extensible Markup Language) は、HTML のようなmarkup language の一つですが、HTML と違って、いろんな種類のフォーマットを対象にできるようなっています。

例えば、XHTML も XML の一種ですし、SVG という画像表示用のフォーマットもあります。こんな感じです。

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
  <circle cx="80" cy="80" r="60" fill="blue" />
  <rect x="100" y="100" width="80" height="80" rx="2" ry="2" fill="red" />  
</svg>

絵としてみると、こんな感じ(表示)。

XML の構成要素は、以下のとおり。

  • 要素(element)
  • 属性(attribute)
  <要素名 属性1="属性値" 属性2="属性値" ...>
      コンテンツ
  </要素名>

要素は、上記のように開始タグから終了タグまでの部分を指し、 その間にコンテンツが入ります。コンテンツの中には、一般の文字列意外にも子要素が入っていても構いません。 コンテンツの含まない要素の場合は、

<要素名 属性1="属性値" 属性2="属性値" ... />

のように表記されます。

一方で、属性値にはネスト構造は許されません。あと、コンテンツ内の並び順には意味がありますが、属性の並び順が違っても同じ意味と見なされます。

Python class への変換 (XML)

XML は、属性や子要素があるので、正直、XML の情報を完全に JSON に変換しようとすると、かなり大変です。 一方で、必要な部分だけ切り取るのなら、意外とやれるかと。 pydanticの XML 拡張 pydantic_xml パッケージを使ってみます。

from typing import List  # Type Hints と呼ばれる型記述用モジュール
from pydantic_xml import BaseXmlModel, attr, element, wrapped  # pip package 名は pydantic-xml


class XFaculty(BaseXmlModel):
    established: int = attr()
    name: str = element(tag='name')  # `name' タグの子要素と対応
    departments: List[str] = wrapped('departments',       # `departments` 要素下の
                                     element(tag='department'))  # `department` 要素に対応(複数個:List)


class XUniv(BaseXmlModel, tag='univ'):
    name: str = element(name='name')  # `name` 要素
    established: int = attr(name='established')  # `established` 属性
    faculties: List[XFaculty] = element(tag='faculty')  # `faculty` 属性

XML 文書のどの属性もしくは要素を取り出して対応させるのか、各種指定を書き込んでいます。

XML 文書から class への変換と、その逆。XML文書から全情報を取り込んでいるわけではないので、元のXML文書に戻るわけではないですよ。

# doc (XML 相当の str) から univ: XUniv への変換
univ = XUniv.from_xml(doc)
# univ から json document 相当の bytes に変換
bytes2 = univ.to_xml(encoding='utf-8')

ついでに、入力 XML がこちらの意図したフォーマットになっていない場合は、変換に失敗してくれます(変な文書をフィルターできる)。

DOM

一方で、任意の XML を扱いたい場合、XML を木構造として扱います。XML を DOM (Document Object Model) とよばれる木構造データ構造に変換してつかうのが一般的です。 DOM は、JavaScript などを持ちいて動的 Web Page を作成するのにもつかわれます。 ブラウザは、(X)HTML 文書を DOM データ構造に変換して持っています。 そのうえで、JavaScript から DOM データ構造に対する改変を許します。 これによって、HTML ページが動けるようになるわけです。

HTML 文書を変更すると表示が変わるというと奇異な感じがするかもしれませんが、HTML 文書をもとに、HTML の木構造= GUI 部品群が配置された、ある種の GUI アプリみたいなものです。

スキーマ

ここまで JSON にしろ XML をにしろ、任意の JSON や XML を相手にするのではなく、「ある種の目的」のための「ある種のフォーマット」にそった JSON や XML を相手にしてました。対応する Java のデータ構造が決まっているように、文書も「ある種のフォーマット」に従っている訳です。こういう文書の論理的構造のことを、スキーマ (Schema)とよびます。

Schema を記述するための言語もあって、XML では DTD や XML Schema, RELAX NG などが有名で、JSON だと JSON Schema などがあります。

授業の際は、簡単に例をあげて紹介します。