Returns the path to a JSON object that is part of a linked list structure.
The path returned would be suitable for input to json_get_by_path and related routines.
Note
If an error occurs (which in this case means a malformed
JSON structure) then an exception will be thrown, unless
found
is present, which will be set to false
. path
will be a blank string.
Note
If json%path_mode/=1
, then the use_alt_array_tokens
and path_sep
inputs are ignored if present.
Note
http://goessner.net/articles/JsonPath/ (path_mode=3
)
does not specify whether or not the keys should be escaped (this routine
assumes not, as does http://jsonpath.com).
Also, we are using Fortran-style 1-based array indices,
not 0-based, to agree with the assumption in path_mode=1
Type | Intent | Optional | Attributes | Name | ||
---|---|---|---|---|---|---|
class(json_core), | intent(inout) | :: | json | |||
type(json_value), | intent(in), | pointer | :: | p |
a JSON linked list object |
|
character(kind=CK, len=:), | intent(out), | allocatable | :: | path |
path to the variable |
|
logical(kind=LK), | intent(out), | optional | :: | found |
true if there were no problems |
|
logical(kind=LK), | intent(in), | optional | :: | use_alt_array_tokens |
if true, then ‘()’ are used for array elements
otherwise, ‘[]’ are used [default]
(only used if |
|
character(kind=CK, len=1), | intent(in), | optional | :: | path_sep |
character to use for path separator
(otherwise use |
subroutine json_get_path(json, p, path, found, use_alt_array_tokens, path_sep) implicit none class(json_core),intent(inout) :: json type(json_value),pointer,intent(in) :: p !! a JSON linked list object character(kind=CK,len=:),allocatable,intent(out) :: path !! path to the variable logical(LK),intent(out),optional :: found !! true if there were no problems logical(LK),intent(in),optional :: use_alt_array_tokens !! if true, then '()' are used for array elements !! otherwise, '[]' are used [default] !! (only used if `path_mode=1`) character(kind=CK,len=1),intent(in),optional :: path_sep !! character to use for path separator !! (otherwise use `json%path_separator`) !! (only used if `path_mode=1`) character(kind=CK,len=:),allocatable :: name !! variable name character(kind=CK,len=:),allocatable :: parent_name !! variable's parent name character(kind=CK,len=max_integer_str_len) :: istr !! for integer to string conversion !! (array indices) type(json_value),pointer :: tmp !! for traversing the structure type(json_value),pointer :: element !! for traversing the structure integer(IK) :: var_type !! JSON variable type flag integer(IK) :: i !! counter integer(IK) :: n_children !! number of children for parent logical(LK) :: use_brackets !! to use '[]' characters for arrays logical(LK) :: parent_is_root !! if the parent is the root character(kind=CK,len=1) :: array_start !! for `path_mode=1`, the character to start arrays character(kind=CK,len=1) :: array_end !! for `path_mode=1`, the character to end arrays logical :: consecutive_arrays !! check for array of array case integer(IK) :: parents_parent_var_type !! `var_type` for parent's parent !optional input: if (present(use_alt_array_tokens)) then use_brackets = .not. use_alt_array_tokens else use_brackets = .true. end if if (json%path_mode==1_IK) then if (use_brackets) then array_start = start_array array_end = end_array else array_start = start_array_alt array_end = end_array_alt end if end if ! initialize: consecutive_arrays = .false. if (associated(p)) then !traverse the structure via parents up to the root tmp => p do if (.not. associated(tmp)) exit !finished !get info about the current variable: call json%info(tmp,name=name) if (json%path_mode==2_IK) then name = encode_rfc6901(name) end if ! if tmp a child of an object, or an element of an array if (associated(tmp%parent)) then !get info about the parent: call json%info(tmp%parent,var_type=var_type,& n_children=n_children,name=parent_name) if (json%path_mode==2_IK) then parent_name = encode_rfc6901(parent_name) end if if (associated(tmp%parent%parent)) then call json%info(tmp%parent%parent,var_type=parents_parent_var_type) consecutive_arrays = parents_parent_var_type == json_array .and. & var_type == json_array else consecutive_arrays = .false. end if select case (var_type) case (json_array) !get array index of this element: element => tmp%parent%children do i = 1, n_children if (.not. associated(element)) then call json%throw_exception('Error in json_get_path: '//& 'malformed JSON structure. ',found) exit end if if (associated(element,tmp)) then exit else element => element%next end if if (i==n_children) then ! it wasn't found (should never happen) call json%throw_exception('Error in json_get_path: '//& 'malformed JSON structure. ',found) exit end if end do select case(json%path_mode) case(3_IK) ! JSONPath "bracket-notation" ! example: `$['key'][1]` ! [note: this uses 1-based indices] call integer_to_string(i,int_fmt,istr) if (consecutive_arrays) then call add_to_path(start_array//trim(adjustl(istr))//end_array,CK_'') else call add_to_path(start_array//single_quote//parent_name//& single_quote//end_array//& start_array//trim(adjustl(istr))//end_array,CK_'') end if case(2_IK) ! rfc6901 ! Example: '/key/0' call integer_to_string(i-1_IK,int_fmt,istr) ! 0-based index if (consecutive_arrays) then call add_to_path(trim(adjustl(istr))) else call add_to_path(parent_name//slash//trim(adjustl(istr))) end if case(1_IK) ! default ! Example: `key[1]` call integer_to_string(i,int_fmt,istr) if (consecutive_arrays) then call add_to_path(array_start//trim(adjustl(istr))//array_end,path_sep) else call add_to_path(parent_name//array_start//& trim(adjustl(istr))//array_end,path_sep) end if end select if (.not. consecutive_arrays) tmp => tmp%parent ! already added parent name case (json_object) if (.not. consecutive_arrays) then ! idea is not to print the array name if ! it was already printed with the array !process parent on the next pass select case(json%path_mode) case(3_IK) call add_to_path(start_array//single_quote//name//& single_quote//end_array,CK_'') case default call add_to_path(name,path_sep) end select end if case default call json%throw_exception('Error in json_get_path: '//& 'malformed JSON structure. '//& 'A variable that is not an object '//& 'or array should not have a child.',found) exit end select else !the last one: select case(json%path_mode) case(3_IK) call add_to_path(start_array//single_quote//name//& single_quote//end_array,CK_'') case default call add_to_path(name,path_sep) end select end if if (associated(tmp%parent)) then !check if the parent is the root: parent_is_root = (.not. associated(tmp%parent%parent)) if (parent_is_root) exit end if !go to parent: tmp => tmp%parent end do else call json%throw_exception('Error in json_get_path: '//& 'input pointer is not associated',found) end if !for errors, return blank string: if (json%exception_thrown .or. .not. allocated(path)) then path = CK_'' else select case (json%path_mode) case(3_IK) ! add the outer level object identifier: path = root//path case(2_IK) ! add the root slash: path = slash//path end select end if !optional output: if (present(found)) then if (json%exception_thrown) then found = .false. call json%clear_exceptions() else found = .true. end if end if contains subroutine add_to_path(str,path_sep) !! prepend the string to the path implicit none character(kind=CK,len=*),intent(in) :: str !! string to prepend to `path` character(kind=CK,len=*),intent(in),optional :: path_sep !! path separator (default is '.'). !! (ignored if `json%path_mode/=1`) select case (json%path_mode) case(3_IK) ! in this case, the options are ignored if (.not. allocated(path)) then path = str else path = str//path end if case(2_IK) ! in this case, the options are ignored if (.not. allocated(path)) then path = str else path = str//slash//path end if case(1_IK) ! default path format if (.not. allocated(path)) then path = str else ! shouldn't add the path_sep for cases like x[1][2] ! [if current is an array element, and the previous was ! also an array element] so check for that here: if (.not. ( str(len(str):len(str))==array_end .and. & path(1:1)==array_start )) then if (present(path_sep)) then ! use user specified: path = str//path_sep//path else ! use the default: path = str//json%path_separator//path end if else path = str//path end if end if end select end subroutine add_to_path end subroutine json_get_path