2
0

JSON-FortranのAPIドキュメントを読むための解説

Last updated at Posted at 2024-05-28

初めに

自力でこのAPIドキュメントからライブラリの使用方法を見つけ出し、コードを作成することを目指した解説になります。

3つの派生型

基本的にDerived Typesの箇所を見るだけで十分です。

こちらを見るとわかる通り3つの派生型(この場合は他言語ではclassに相当するもの)があり、この3つを利用してプログラムコードを作成します。

それぞれの派生型の内容

  • json_value:データ構造の本体、json形式で格納されたデータをpointerを駆使してfortranのデータ構造に落とし込んだもの。親や子や自分自身の前後に対してpointerを張ってデータの構造を作成している。(ライブラリの利用者はこの内部のデータを直接読み取ることはできず、json_coreを利用する必要がある。)
  • json_core:出力する時の表示形式の設定以外の内部のデータをほぼ保持しない。関数的な操作で、json_valueを直接加工したり、ファイルとjson_valueの相互変換や文字列型とjson_valueの相互変換を行う。
  • json_file:json_valuejson_coreがprivateで組み込まれており、ファイル読み込んで内部でjson_valueを組み立て、内部でjson_coreを利用している。

方針

jsonファイルを読み込む場合(一部例外あり)またはjson_valueを作らなくても済むような簡単なjsonファイルを作成したい場合はjson_fileを利用する。
jsonファイルを作成したい場合はjson_coreを利用する。
データが構造を持つものはjson_valueを使用する。

つまり、簡単な場合はjson_file、複雑な場合はjson_corejson_valueを使う。

用語

  • p: json_valueのポインタ
  • path: jsonの要素を指し示す名称 例:'inputs.flag'
  • val: jsonの要素
  • vec: jsonの要素のうち配列をさす

json_valueのポインタの内容

こちらにある通り、各ポインタには自身の名前、データ、親のポインタ、1番目の子のポインタ情報が格納されている。
下記の例で実際にポインタの状態を見てみる。
ポイントはどれが親と子の関係なのか、名前のありなしである。

{
  "id": 1,
  "name": "Banana",
  "price": 12.50,
  "tags": [ "home", "green" ],
  "list": [
    {
      "lon": 130,
      "lat": 34
    },
    {
      "lon": 131,
      "lat": 35
      }
  ]
}

この例だとrootは名前なしオブジェクト("")、rootの子供はすべて名前があり並列で"id": 1->"name": "Banana"->"price": 12.50->"tags": [ "home", "green" ]-> "list": [ { "lon": 130, "lat": 34 }, { "lon": 131, "lat": 35 } ]の順に並んでいる。
tagshomeは親子関係になっており、tagsはtagsという名前を持った配列であり、homeは名前をもたない文字列となる。
list{"lon": 130,"lat": 34}は親子関係になっており、listはlistという名前を持った配列であり、{"lon": 130,"lat": 34}は名前を持たないオブジェクトになる。

json_coreのType-Bound Proceduresの読み方

Type-Bound Proceduresは直訳すると型に束縛されたプロシージャであるが、オブジェクト指向におけるオブジェクトのメソッドと思ってよい。

add Procedures を例に

image.png

generic, public :: add => ...と書かれてあるので、総称名であり引数の型や要素数に応じて実行される中身が異なる。
総称名がpublic、個別名がprivateなので、総称名でのみしか実行できないようになっている。
具体的に個別名json_value_add_integerプロシージャを見ると

image.png

引数が4つあるが、Type-Bound Proceduresなので1つ目は派生型自身を指しており、引数として入力する際は(p, name, val)となる。
json_value型のpの子供に名前がnameの整数値valを加えるという動作を行っている。

作成方法その1

ここでは派生型json_coreを利用して派生型json_valueのポインタの構築を行い、それをファイルに出力するまでのプログラムを構築する。

program test
    use json_module
    implicit none
    type(json_core) :: json
    type(json_value), pointer :: root_p, list_p, tmp_p

    ! list_pの作成
    call json%create_array(list_p, 'list')
    call json%create_object(tmp_p, '')
    call json%add(tmp_p, 'lon', 130)
    call json%add(tmp_p, 'lat', 34)
    call json%add(list_p, tmp_p)
    call json%deserialize(tmp_p, '{"lon":131,"lat":35}') ! json文字列からjson_value型へ
    call json%add(list_p, tmp_p)

    ! root_pの作成
    call json%create_object(root_p, '')
    call json%add(root_p, "id", 1)
    call json%add(root_p, "name", "Banana")
    call json%add(root_p, "price", 12.50)
    call json%add(root_p, "tags", [character(len=5):: "home", "green"], trim_str=.true.) ! 文字列の配列も可
    call json%add(root_p, list_p)

    ! ファイルに出力
    call json%print(root_p, "file.json")

    ! ポインタに格納されたデータを消去
    call json%destroy(root_p)

end program test

方針

まずはlistという名前の付いた配列のjson_value型を作成する。
そのうえで、名前なしオブジェクト({})を作成し、その中に子供を付け加える。

create系プロシージャ

type(json_core) :: json
type(json_value), pointer :: root_p, test_p, list_p
call json%create_object(root_p, '') ! 名前なしオブジェクト
call json%create_object(test_p, 'test') ! 名前ありオブジェクト
call json%create_array(list_p, 'list') ! 配列

オブジェクトまたは配列のポインタを作成する。
それ以外要素もcreate系プロシージャで作成できるようであるが、オブジェクトまたは配列の子供として存在するはずなので、add系プロシージャで付け加えればよい。

add系プロシージャ

call json%add(root_p, "id", 1)
call json%add(root_p, list_p) !pointerにpointerを加える場合。

また上記の作成例のようにdeserializeプロシージャを用いればjson文字列から直接json_valueに変換が可能になるので、簡単なものはそれで作成が可能である。

作成方法その2

派生型json_fileのみを利用して、jsonファイルに出力するまでのプログラムを構築する。

program test
    use, intrinsic :: iso_fortran_env, only: int32
    use json_module
    implicit none
    type(json_file) :: json_f
    integer(int32) :: lon(2) = [130, 131]
    integer(int32) :: lat(2) = [34, 35]
    integer(int32) :: i
    character(1) :: num

    call json_f%initialize() ! 内部のjson_valueをリセット
    call json_f%add('id', 1)
    call json_f%add('name', 12.50)
    call json_f%add('tags', [character(len=5):: "home", "green"], trim_str=.true.)

    do i = 1, size(lon)
        write (num, '(I1)') i
        call json_f%add('list('//num//').lon', lon(i))
        call json_f%add('list('//num//').lat', lat(i))
    end do

    call json_f%print("file.json") ! ファイルに出力
    call json_f%destroy() ! 内部のjson_valueを破壊

end program test

add系プロシージャ

call json_f%add('list(1).lon', 134)

pathjson_valueでの入力のみ。したがってjson_fileのみを利用する場合はpathだけで簡単に構築できるかどうかが目安になる。

printプロシージャ

call json_f%print() !コンソール出力
call json_f%print("file.json") !ファイル出力
call json_f%print(iunit=12) !file unit numberへの出力

引数の型に応じて出力先が変わる。

配列の中にオブジェクトが入っているjsonファイルを読み取る場合

配列の中にオブジェクトが入っているjsonファイル(下記のような例)の場合はjson_fileのみでの読みよりに苦労する。
派生型json_fileからは直接指定されたpathの子供の数の読み取りができないからである。

file.json
{
  "id": 1,
  "name": "Banana",
  "price": 12.50,
  "tags": [ "home", "green" ],
  "list": [
    {
      "lon": 130,
      "lat": 34
    },
    {
      "lon": 131,
      "lat": 35
      }
  ]
}

方針

listの要素数が決まっている場合はpathで直接すれば済むのでjson_fileのみで問題がない。
listの要素数が決まっていない場合は以下の2通りで行う。

  1. json_fileでデータを取得して、json_valueで出力可能なため、一部のデータをjson_valueで出力してjson_coreで読み取る。
  2. 最初からjson_coreを使う。

1つ目の方法

program test
    use, intrinsic :: iso_fortran_env, only: int32
    use json_module
    implicit none
    type(json_file) :: json_f
    type(json_core) :: json_c
    type(json_value), pointer :: p, p_list_child
    integer(int32), allocatable :: lon(:)
    integer(int32), allocatable :: lat(:)
    integer(int32) :: id
    integer(int32) :: num
    integer(int32) :: i

    call json_f%initialize() ! 内部のjson_valueをリセット
    call json_f%load('file.json')

    call json_f%get(path='id', val=id)
    call json_f%get(path="list", p=p) !ポインタに出力
    num = json_c%count(p=p)
    allocate (lon(num))
    allocate (lat(num))
    do i = 1, num
        call json_c%get_child(p=p, idx=i, child=p_list_child)
        call json_c%get(me=p_list_child, path="lon", value=lon(i))
        call json_c%get(me=p_list_child, path="lat", value=lat(i))
    end do
    print *, lon
    call json_f%destroy() ! 内部のjson_valueを破壊

end program test

2つ目の方法

program test
    use, intrinsic :: iso_fortran_env, only: int32
    use json_module
    implicit none
    type(json_core) :: json_c
    type(json_value), pointer :: p, p_list, p_list_child
    integer(int32), allocatable :: lon(:)
    integer(int32), allocatable :: lat(:)
    integer(int32) :: id
    integer(int32) :: num
    integer(int32) :: i

    call json_c%load(file='file.json', p=p)
    call json_c%get(me=p, path='id', value=id)
    print *, id
    call json_c%get(me=p, path='list', p=p_list)

    num = json_c%count(p=p_list)
    allocate (lon(num))
    allocate (lat(num))
    do i = 1, num
        call json_c%get_child(p=p_list, idx=i, child=p_list_child)
        call json_c%get(me=p_list_child, path="lon", value=lon(i))
        call json_c%get(me=p_list_child, path="lat", value=lat(i))
    end do
    print *, lon, lat
    call json_c%destroy(p)

end program test

終わりに

エラー処理は特に書きませんでしたが必要に応じてAPIドキュメントを読んで書きましょう。

2
0
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
2
0