FortranでJSONを扱う方法を探していたところ、gitにJSONパーサーがあったので試してみました。
その使い方をご紹介いたします。
目次
JSONパーサーのダウンロード
githubで公開されているfortranで記述されたJSONのパーサー(json-fortran)を使用します。githubから以下のコマンドで関連ファイルを入手します。
$ git clone https://github.com/jacobwilliams/json-fortran
lsコマンドを打つと、以下の構成になっています。
$ cd json-fortran
$ ls
CHANGELOG.md build.sh fpm.toml json-fortran.pc.cmake.in visual_studio
CMakeLists.txt cmake json-fortran.code-workspace media
LICENSE codecov.yml json-fortran.fobis pages
README.md files json-fortran.md src
ディレクトリsrcの中にfortranのソースコードが含まれています。また、ディレクトリfilesの中にはJSONファイルのサンプルが含まれています。json-fortranを使用するには、srcの中のソースコードのみが必要です。
$ cd src
$ ls
json_file_module.F90 json_initialize_arguments.inc json_module.F90 tests
json_get_scalar_by_path.inc json_initialize_dummy_arguments.inc json_parameters.F90
json_get_vec_by_path.inc json_kinds.F90 json_string_utilities.F90
json_get_vec_by_path_alloc.inc json_macros.inc json_value_module.F90
使用するためにコンパイルして実行体を作成します。ここでは、Intel oneAPIのifortを使用しました。gfortranでもコンパイル可能です。
コンパイルに用いるmakefileを以下のように作成し、ディレクトリsrcの中に配置しました。
prog:=test
main_src:=./tests/jf_test_01.F90
json_src:= json_kinds.F90 \
json_parameters.F90 \
json_string_utilities.F90 \
json_value_module.F90 \
json_file_module.F90 \
json_module.F90
json_objs:=$(json_src:%.F90=%.o)
opt:=-O2
all: $(prog)
ifort $(opt) -o $(prog) $(main_src) -ljson -L./lib -I./include
@#gfortran $(opt) -o $(prog) $(main_src) -ljson -L./lib -I./include
$(prog):
@-mkdir -p include
@-mkdir -p lib
ifort $(opt) -c $(json_src) -module ./include
@#gfortran $(opt) -c $(json_src) -J ./include
ar cr libjson.a $(json_objs)
mv libjson.a ./lib
clean:
rm -rf *.a *.o *.mod lib include $(prog)
gfortranを使用する場合はifortの行をコメントアウトして、gfortranの行のコメントアウトを外してください。makeコマンドを実行すると、デフォルトではテスト用ファイル./tests/jf_test_01.F90がコンパイルされて、実行体testが作成されます。
$ make
コンパイルするファイルを変更する場合には、main_srcにメインプログラムのファイル名を設定します。例えば、テスト用ファイルjf_test_02.F90を実行する場合には以下のようにします。
$ make main_src=./tests/jf_test_02.F90
また、作成されたファイルを削除して元の状態に戻したい場合には、
$ make clean
とします。
以上で、JSONパーサーを用いて色々試してみる環境ができました。
具体的には、test.f90などの適当な名前のファイルを作成し、main_srcにその名前を与えれば、動作確認を行うことができます。
プログラムの構成
基本構成
構造体を定義し、initialize()で初期化、destroy()で破棄します。初期化と破棄の間に処理したい内容を記述します。
! JSONファイルを作成する場合
program main
use json_module
implicit none
type(json_core) :: json
call json%initialize()
! ここに処理を記述
call json%destroy()
end program
! JSONファイルを読み込む場合
program main
use json_module
implicit none
! JSONファイルを読み込む場合
type(json_file) :: jsonfile
call jsonfile%initialize()
call jsonfile%load(filename = 'sample.json')
! ここに処理を記述
call jsonfile%destroy()
end program
データの種類
json-fortranのjson_kindsモジュールの中で、データ型のメモリサイズ(kind)が定義されています。それをjson_moduleの中で別名(alias)に変更しています。
下の表にその概要をまとめます。詳細はjson_kindsモジュールをご確認ください。
データタイプ | json_module内での変数名 | json_kindsモジュール内での変数名 | 参考 |
整数型 | json_IK | IK | デフォルトではint32(4 bytes)が使用されるが、コンパイルオプションにより変更できる。 例えば、int64(8 bytes)を使用する場合には、以下のオプションを追加する。 -DINT64 |
実数型 | json_RK | RK | デフォルトではreal64(8 bytes)が使用されるが、コンパイルオプションにより変更できる。 例えば、real128(16 bytes)を使用する場合には、以下のオプションを追加する。 -DREAL128 |
論理型 | json_LK | LK | デフォルトのlogical kindが使用される。 IntelとGfortranコンパイラでは 4 bytes である。 |
文字列型 | json_CK | CK | デフォルトではプロセッサのDEFAULTのcharacter kindが使用される。 IntelとGfortranコンパイラでは 1 byte である。 ISO 10646 (UCS4) をサポートしている場合には、コンパイル時にgfortranのコンパイルオプションに -D__GFORTRAN__ -DUSE_UCS4 を追加することでISO 10646のサポートを使用できる。 この場合は4 bytesになる。このサポートは、gfortran >= 4.9.2で使用できる。 |
使い方(JSONファイルの読込)
JSONファイルからデータを読み込む方法について示します。
読み込みのテストとして、次のJSONファイルを使用します(ファイル名はsample.jsonとします)。
{
! 色々なデータタイプを含むオブジェクト
"data1" : {
"int" : 3,
"int array" : [3, 4, 5],
"real" : 1.0d0,
"real array" : [4.0d0, 6.0d0, 8.0d0],
"char" : "hoge",
"char array" : ["hoge1", "hoge2", "hoge3"],
"logical" : true,
"logical array" : [true, false, true]
},
! オブジェクトの中にオブジェクトの配列がある場合
"data2" : [
{
"no" : 1
},
{
"no" : 2
}
]
}
JSONファイルからデータを読み込むには、get関数を使用します。get関数の第1引数には読み込みたいデータのタグを、第2引数には読み込んだ値を格納する変数を指定します。
タグは、パスセパレーター(デフォルトではドット(.))でオブジェクト名と変数名を接続することで作成します。
また、オブジェクトの中に配列がある場合には、名前の後ろに括弧()をつけ、その中に要素番号を入れることでタグを作成します。
以下に、上記のサンプルのJSONファイルを読み込み、標準出力するプログラムを記述します。
program main
use json_module
use json_module, IK => json_IK, &
RK => json_RK, &
CK => json_CK, &
LK => json_LK
implicit none
type(json_file) :: jsonfile
integer, parameter :: io_unit = 6 ! 標準出力用の装置番号
! 読み込んだ値を格納する変数
integer(IK) :: ival ! 整数型
integer(IK), allocatable :: ia(:) ! 整数型配列
real(RK) :: rval ! 実数型
real(RK), allocatable :: ra(:) ! 実数型配列
character(kind=CK,len=:), allocatable :: cval ! 文字列
character(kind=CK,len=:), allocatable :: ca(:) ! 文字列型配列
integer(IK), allocatable :: icalen(:) ! 文字列型配列の各文字列の長さを格納
logical(LK) :: lval ! 論理型
logical(LK), allocatable :: la(:) ! 論理型配列
! ファイルの読込
call jsonfile%initialize()
call jsonfile%load(filename = 'sample.json')
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
endif
! データの読込と標準出力
! 整数の場合
call jsonfile%get("data1.int", ival)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.int = ', ival
endif
! 整数配列の場合
call jsonfile%get('data1.int array', ia)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.int array = ', ia
endif
! 実数の場合
call jsonfile%get('data1.real', rval)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.real = ', rval
endif
! 実数配列の場合
call jsonfile%get('data1.real array', ra)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.real array = ', ra
endif
! 文字列の場合
call jsonfile%get('data1.char', cval)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.char = ', cval
endif
! 文字列配列の場合
call jsonfile%get('data1.char array', ca, icalen)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.char array = ', ca(1)//' '//ca(2)//' '//ca(3)
endif
! 論理型変数の場合
call jsonfile%get('data1.logical', lval)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.logical = ', lval
endif
! 論理型配列の場合
call jsonfile%get('data1.logical array', la)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data1.logical array = ', la
endif
! オブジェクト内に配列がある場合
call jsonfile%get('data2(1).no', ival)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data2(1).no = ', ival
endif
call jsonfile%get('data2(2).no', ival)
if(jsonfile%failed()) then
call jsonfile%print_error_message(io_unit)
stop
else
write(io_unit, *) 'data2(2).no = ', ival
endif
call jsonfile%destroy()
end program
このプログラム(test.f90)を、
$ make main_src=test.f90
$ ./test
によりコンパイルして実行すると、以下の結果が得られます。
data1.int = 3
data1.int array = 3 4 5
data1.real = 1.00000000000000
data1.real array = 4.00000000000000 6.00000000000000 8.00000000000000
data1.char = hoge
data1.char array = hoge1 hoge2 hoge3
data1.logical = T
data1.logical array = T F T
data2(1).no = 1
data2(2).no = 2
以上により、FortranでJSONファイルを読み込むことができました。
使い方(JSONファイルの出力)
JSONファイルをFortranで作成する方法を示します。
オブジェクトの作成
jsonのオブジェクトの作成はcreate_object()で行います。第1引数にjson_value構造体のポインタ、第2引数にオブジェクトの名前を与えます。次の例ではオブジェクト名が空のrootオブジェクト、つまり、ただの中括弧{}を作成します。
program main
use json_module
implicit none
type(json_core) :: json
type(json_value), pointer :: root ! rootオブジェクト用のポインタ
integer, parameter :: io_unit = 6 ! 標準出力に使用
call json%initialize()
! rootオブジェクトを作成
call json%create_object(root, '')
! 作成したオブジェクトを標準出力
call json%print(root, io_unit)
! 作成したオブジェクトをファイル出力
call json%print(root, 'result.json')
call json%destroy()
end program
結果として、以下の内容のJSONファイルの作成と標準出力が行われます。
{}
データの追加
add()によりオブジェクトにデータを追加します。第1引数にデータを追加するオブジェクト、第2引数にキー、第3引数に値を設定します。値は通常の変数の他、配列やブラケットを用いて与えることもできます。
program main
use json_module
use json_module, IK => json_IK ! JSON-fortranで使用される整数型のメモリサイズ(kind)
implicit none
type(json_core) :: json
type(json_value), pointer :: root ! rootオブジェクト用のポインタ
integer, parameter :: io_unit = 6 ! 標準出力に使用
integer(IK) :: int_scalar = 0 ! 追加するデータ
integer(IK) :: int_vector(3) = (/1, 3, 5/) ! 追加するデータ
call json%initialize()
! エラー処理
if (json%failed()) then
call json%print_error_message(io_unit)
endif
! rootオブジェクトを作成
call json%create_object(root, '')
! rootオブジェクトに整数を追加
call json%add(root, 'int_scalar', int_scalar)
call json%add(root, 'int_vector(odd)', int_vector) ! 配列も追加可能
call json%add(root, 'int_vector(even)', [2_IK, 4_IK, 6_IK]) ! 直接与えることも可能
call json%add(root, 'int_connection', [int_scalar, int_vector]) ! つなぐことも可能
! 作成したオブジェクトを標準出力
call json%print(root, io_unit)
! 作成したオブジェクトをファイル出力
call json%print(root, 'result.json')
call json%destroy()
end program
結果として、以下の内容のJSONファイルの作成と標準出力が行われます。
{
"int_scalar": 0,
"int_vector(odd)": [
1,
3,
5
],
"int_vector(even)": [
2,
4,
6
],
"int_connection": [
0,
1,
3,
5
]
}
また整数以外のデータもJSONファイルに出力できます。例を以下に示します。
program main
use json_module
use json_module, IK => json_IK, &
RK => json_RK, &
CK => json_CK, &
LK => json_LK
implicit none
type(json_core) :: json
type(json_value), pointer :: root ! rootオブジェクト用
integer, parameter :: io_unit = 6 ! 標準出力に使用
integer(IK) :: int_scalar = 5 ! 追加する整数データ
character(kind=CK, len=4) :: char_data = "MOJI" ! 追加する文字列データ
real(RK) :: real_scalar = 1.0d0 ! 追加する実数データ
logical(LK) :: logical_data = .true. ! 追加する論理型データ
call json%initialize()
! エラー処理
if (json%failed()) then
call json%print_error_message(io_unit)
endif
! rootオブジェクトを作成
call json%create_object(root, '')
! rootオブジェクトに整数を追加
call json%add(root, 'int', int_scalar)
call json%add(root, 'real', real_scalar)
call json%add(root, 'char', char_data)
call json%add(root, 'logical', logical_data)
! 作成したオブジェクトを標準出力
call json%print(root, io_unit)
! 作成したオブジェクトをファイル出力
call json%print(root, 'result.json')
call json%destroy()
end program
結果として、以下の内容のJSONファイルの作成と標準出力が行われます。
{
"int": 5,
"real": 0.1E+1,
"char": "MOJI",
"logical": true
}
整数型の場合と同様に、配列やブラケット[]による出力も可能です。ただし、ブラケット[]で変数をつなぐ場合には、同じデータ型である必要があります。
オブジェクトのネスト
オブジェクトにはデータだけではなく、オブジェクト自体も追加できます。オブジェクトの作成と追加には、create_object()とadd()を使用します。また、親のオブジェクトを取得するにはget_parent()を使用します。get_parent()では第1引数に与えたオブジェクトの親へのポインタが、第2引数に代入されます。
program main
use json_module
use json_module, IK => json_IK
implicit none
type(json_core) :: json
type(json_value), pointer :: root ! rootオブジェクト用のポインタ
type(json_value), pointer :: inp
integer, parameter :: io_unit = 6 ! 標準出力に使用
call json%initialize()
! rootオブジェクトを作成
call json%create_object(root, '')
! parametersオブジェクトを作成
call json%create_object(inp, 'parameters')
! rootオブジェクトにparametersオプジェクトを追加
call json%add(root, inp)
! parametersオブジェクトにデータを追加
call json%add(inp, 'data', 3_IK)
! parametersオブジェクトの親をinpに代入
call json%get_parent(inp, inp)
! parametersオブジェクトの親に文字列データを追加
call json%add(inp, 'name', 'fogefoge')
call json%print(root, io_unit)
call json%print(root, 'result.json')
call json%destroy()
end program
このコードでは、まずrootオブジェクトにparametersという名前のオブジェクトを追加し、そこに整数型のデータを追加しました。そして、get_parent()によりparametersオブジェクトの親(root)を取得し、そこに文字列型のデータを追加しました。結果として、以下の内容のJSONファイルの作成と標準出力が行われます。
{
"parameters": {
"data": 3
},
"name": "fogefoge"
}
まとめ
FortranでJSONファイルに記述されたデータを読み書きする方法を示しました。
Fortranにはネームリストとよばれる独自のフォーマットのファイル読み書き方法がありますが、jsonのような入れ子の構造にできないため、データ間の関係が分からりずらく、エラー処理を正しくプログラミングしないと入力ミスが起こりやすい欠点があります。
それに対して、JSONは規格が厳密に決まっており、入れ子構造を採用していることからデータ間の関係もわかりやすいです。
よければ、試してみてください。