How to read integer arrays with `null` elements?
milancurcic opened this issue · 2 comments
This program:
use json_module, only: json_core, json_value
type(json_core) :: json
type(json_value), pointer :: json_val
integer, allocatable :: vec(:)
logical :: found
call json % parse(json_val, '{"a": [1, 2]}')
call json % get(json_val, 'a', vec, found)
print *, found, allocated(vec)
end
produces T T
as expected.
However, this program
use json_module, only: json_core, json_value
type(json_core) :: json
type(json_value), pointer :: json_val
integer, allocatable :: vec(:)
logical :: found
call json % parse(json_val, '{"a": [1, null]}')
call json % get(json_val, 'a', vec, found)
print *, found, allocated(vec)
end
produces F F
, which I didn't expect. I suspect that the null
value in an integer array confuses json-fortran here.
If I declare vec
as real
, then I get NaN for null
elements (also expected), and I can recover my non-null
integer values from the intermediate real
array. Is this your recommended way for getting integer arrays with null
elements, or is there a better way?
As an aside, I suggest json % get
to return found
as .true.
in this scenario, and handle a null
in some other way. I know that there's no appropriate integer value for a null
, but perhaps -huge(vec)
would be OK as long as it's documented.
There is special logic for null
to real
(see the null_to_real_mode
option in json%initialize()
, by default setting null
to NaN
). I never added anything for integers, so that's why you have the behavior you are seeing. If you left out the found
and called json%check_for_errors
you would get the error message "Unable to resolve value to integer".
You can always traverse the array yourself if you expect null
s to be in there. It is tedious, but here is a very basic example:
program test
use json_module, IK => json_IK, LK => json_LK, CK => json_CK
implicit none
type(json_core) :: json
type(json_value), pointer :: json_val
integer, allocatable :: vec(:)
logical :: found
! original:
call json % parse(json_val, '{"a": [1, null]}')
call json % get(json_val, 'a', vec, found)
print *, found, allocated(vec)
! alt version:
call json_get_integer_vec_by_path_with_nulls(json, json_val, 'a', vec, found)
print *, found, allocated(vec), vec
contains
subroutine json_get_integer_vec_by_path_with_nulls(json, p, path, vec, found)
!! returns an integer vector, but with any nulls replaced with -huge
implicit none
type(json_core),intent(inout) :: json
type(json_value),pointer,intent(in) :: p
character(kind=CK,len=*),intent(in) :: path
integer(IK),dimension(:),allocatable,intent(out) :: vec
logical(LK),intent(out),optional :: found
integer(IK) :: var_type, n_children
integer :: i !! counter
type(json_value),pointer :: p_array, p_element
logical(LK) :: error
if (json%failed()) return
call json%get(p, path, vec, found)
if (present(found)) then
error = .not. found
else
error = json%failed()
end if
if (error) then
call json%clear_exceptions()
call json%info(p, path=path, found=found, var_type=var_type, n_children=n_children)
if (found .and. var_type==json_array) then
call json%get(p, path, p_array) ! pointer to array
allocate(vec(n_children))
do i = 1, n_children
call json%get_child(p_array, i, p_element) ! pointer to an element
! if an integer, get value, if null, set "nan" value:
call json%info(p_element, var_type = var_type)
select case (var_type)
case(json_integer)
call json%get(p_element, vec(i))
case(json_null)
vec(i) = -huge(vec)
case default
error stop 'not an integer or null'
end select
end do
else
if (present(found)) found = .false.
end if
end if
end subroutine json_get_integer_vec_by_path_with_nulls
end program test
which prints:
F F
T T 1 -2147483647
I could add a similar null_to_integer_mode
mode for integers. I'd need to think about making -huge
be the default, since it seems less obvious than Null to NaN to me. But maybe it is OK.
Thanks. I didn't know about check_for_errors
, will start using it. Reading as reals and casting to integers does the job for me.