Searching for a User-Friendly JSON Library

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:

{
    "choices": [
        {
            "text1": "Fortran",
            "text2": "Fortran"
        }
    ]
}

Maybe something similar to this code is possible?

type(type_json)               :: json
character(len=:), allocatable :: jsonstr

jsonstr    = '{"choices": [{"text1": "Fortran", "text2": "Fortran"}]}'
text_value = json%get(jsonstr, "choices%text1")

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.

With json-fortran you could do this:

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.

5 Likes

Thanks, @jacobwilliams . That’s exactly what I wanted. :slightly_smiling_face:

And maybe another question: Which procedures can be used to import (read) and export (write) JSON files?

See json_file.load and json_file.print.

1 Like

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.

1 Like

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. :slight_smile:

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.

I see. I was thinking more about JSON as user input. I’ve been using it that way in a few projects.

I would have thought the reverse, but makes sense for writing to/reading from some API service.

I’ll have to think a bit about how it would look to support it in rojff.

1 Like