I am currently working on a Fortran project and need to extract values from JSON data. I am looking for suggestions on a Fortran library that is user-friendly and available through fpm. I found some libraries, including json-fortran, rojff, fson, and fortjson. However, it seems that some of them are not as user-friendly as I had hoped, or I couldn’t find a simple example.
For instance, I need to get the value associated with “text1” from the following JSON snippet:
I use json-fortran and for my simple usage (reading in an input .JSON file to kickstart the simulation) it works rather well.
I have mainly used it to parse files, so my knowledge on strings may not be correct. However, I think that the json variable in your code needs to be initialised with that string before calling the get method.
program example
use json_module
implicit none
type(json_file) :: json
character(len=:), allocatable :: jsonstr
json = '{"choices": [{"text1": "Fortran", "text2": "Fortran"}]}'
call json%get("choices(1).text1", jsonstr)
write(*,*) jsonstr
end program example
Results is:
Fortran
If you wanted to use the % character for the path separator instead of the ., then you can call this first to set that:
call json%initialize(path_separator='%')
Also note that all the get routines have an optional found output you can use to check if the variable was really there. In the example you posted, choices%text1 is not found because that variable doesn’t exist, you need the (1) since it’s the first element of a list.
Your example would look like the following in rojff:
use iso_varying_string, only: char
use rojff
type(fallible_json_value_t) :: maybe_json, maybe_choices
maybe_json = parse_json_from_string('{"choices": [{"text1": "Fortran", "text2": "Fortran"}]}'
if (.not. maybe_json%failed()) then
select type (whole_object => maybe_json%value_)
type is (json_object_t)
maybe_choices = whole_object%get("choices")
if (.not. maybe_choices%failed()) then
select type (choices_array => maybe_choices%value_)
type is (json_array_t)
maybe_first_choice = choices_array%get(1)
if (.not. maybe_first_choice%failed()) then
select type (first_choice => maybe_first_choice%value_)
type is (json_object_t)
maybe_text1 = first_choice%get("text1")
if (.not. maybe_text1%failed()) then
select type (text1 => maybe_text1%value_)
type is (json_string_t)
print *, text1%string ! Will print "Fortran"
class default
error stop "text1 component of first element of choices array was not a string"
end select
else
error stop char(maybe_text1%errors%to_string()) ! Most likely that key wasn't present
end if
class default
error stop "First element of choices wasn't an object"
end select
else
error stop char(maybe_first_choice%errors%to_string()) ! Most likely there was no first element of the array
end if
class default
error stop "choices wasn't an array"
end select
else
error stop char(maybe_choices%errors%to_string()) ! Most likely that key wasn't present
end if
class default
error stop "JSON data wasn't an object"
end select
else
error stop char(maybe_json%errors%to_string()) ! most likely wasn't valid JSON
end if
I’ll admit that doesn’t seem as “user friendly”, but it’s not letting you make assumptions that everything will just work™, and “highly encouraging” you to be nicer to users in letting them know at what point something went wrong. I don’t want to knock @jacobwilliams library, because I know it has it’s uses and looks nicer, but if you just do
and anything goes wrong, you won’t know which of the eight possible things did in fact go wrong, while with rojff you definitely will know. Again, I don’t want to knock it entirely, and I’m sure you could use his library like mine to check for errors at every stage.
The way I’d put it is json-fortran allows for quick prototyping, while rojff encourages robust/defensive programming.
I don’t agree with your last statement. json-fortran has the ability to do all the error checking you are doing here. The syntax is different of course (e.g., see failed, check_for_errors for general error reporting, and info for querying the type/size/etc of a variable). In general, json_file is the high-level “easy” interface. There is another low-level interface (see json_core/json_value) that involves dealing with pointers. I don’t use polymorphism the way you do in rojff because json-fortran dates to a time when there was a major bug in the Intel Fortran compiler that prevented me from using that.
I’m sure it does, and I said as much. I’m just saying json-fortran also makes it easy to ignore all those potential errors, which I admit does have benefits at times. rojff makes it harder to (or at least more obvious when you do) ignore potential errors.
In general, the structure of JSON payloads from production services will be predictable (fields that may be present will be present) and their values will be statically typed. So all the detailed run-time checking of JSON field presence, types, and values is more relevant for prototyping than production use. Of course, this is not a hard rule and services are allowed to return any JSON, but their API docs generally document this either way.
As a user, I find it attractive about json-fortran that it allows both easy get by string pattern as well as error checking if desired.