Skip to content

Commit 2c68f19

Browse files
committed
added experimental JSONPath "bracket-notation" mode for get_path routine. See #266.
1 parent b0dd61f commit 2c68f19

File tree

3 files changed

+94
-14
lines changed

3 files changed

+94
-14
lines changed

src/json_parameters.F90

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ module json_parameters
5252
character(kind=CK,len=*),parameter :: dot = CK_'.' !! for [[json_get_by_path]]
5353
character(kind=CK,len=*),parameter :: tilde = CK_'~' !! RFC 6901 escape character
5454
character(kind=CK,len=*),parameter :: percent = CK_'%' !! Fortran path separator
55+
character(kind=CK,len=*),parameter :: single_quote = CK_"'" !! for JSONPath bracket-notation
5556
character(kind=CK,len=*),parameter :: bspace = achar(8, kind=CK)
5657
character(kind=CK,len=*),parameter :: horizontal_tab = achar(9, kind=CK)
5758
character(kind=CK,len=*),parameter :: newline = achar(10, kind=CK)

src/json_value_module.F90

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,25 +186,25 @@ module json_value_module
186186
!! type is different from the return type (for
187187
!! example, integer to double).
188188

189-
logical(LK) :: trailing_spaces_significant = .false. !! for name and path comparisons, is trailing
190-
!! space to be considered significant.
189+
logical(LK) :: trailing_spaces_significant = .false. !! for name and path comparisons, if trailing
190+
!! space is to be considered significant.
191191

192-
logical(LK) :: case_sensitive_keys = .true. !! for name and path comparisons, are they
193-
!! case sensitive.
192+
logical(LK) :: case_sensitive_keys = .true. !! if name and path comparisons
193+
!! are case sensitive.
194194

195195
logical(LK) :: no_whitespace = .false. !! when printing a JSON string, don't include
196196
!! non-significant spaces or line breaks.
197197
!! If true, the entire structure will be
198198
!! printed on one line.
199199

200-
logical(LK) :: unescaped_strings = .true. !! If false, then the raw escaped
200+
logical(LK) :: unescaped_strings = .true. !! If false, then the escaped
201201
!! string is returned from [[json_get_string]]
202202
!! and similar routines. If true [default],
203203
!! then the string is returned unescaped.
204204

205205
logical(LK) :: allow_comments = .true. !! if true, any comments will be ignored when
206206
!! parsing a file. The comment token is defined
207-
!! by the `comment_char` string.
207+
!! by the `comment_char` character variable.
208208
character(kind=CK,len=1) :: comment_char = CK_'!' !! comment token when
209209
!! `allow_comments` is true.
210210
!! Examples: '`!`' or '`#`'.
@@ -215,6 +215,8 @@ module json_value_module
215215
!! * 1 -- Default mode (see [[json_get_by_path_default]])
216216
!! * 2 -- as RFC 6901 "JSON Pointer" paths
217217
!! (see [[json_get_by_path_rfc6901]])
218+
!! * 3 -- JSONPath "bracket-notation" (currently only
219+
!! used in [[json_get_path]])
218220

219221
character(kind=CK,len=1) :: path_separator = dot !! The `path` separator to use
220222
!! in the "default" mode for
@@ -888,7 +890,7 @@ subroutine json_initialize(me,verbose,compact_reals,&
888890
if (present(unescape_strings)) &
889891
me%unescaped_strings = unescape_strings
890892
if (present(path_mode)) then
891-
if (path_mode==1_IK .or. path_mode==2_IK) then
893+
if (path_mode==1_IK .or. path_mode==2_IK .or. path_mode==3_IK) then
892894
me%path_mode = path_mode
893895
else
894896
me%path_mode = 1_IK ! just to have a valid value
@@ -5512,6 +5514,9 @@ end subroutine json_value_print
55125514
!
55135515
! * The original JSON-Fortran defaults
55145516
! * [RFC 6901](https://tools.ietf.org/html/rfc6901)
5517+
!
5518+
!@warning if `found` is present, we should clear any exceptions that are thrown
5519+
! to be consistent with other routines. This is not currently being done.
55155520

55165521
subroutine json_get_by_path(json, me, path, p, found)
55175522

@@ -5524,16 +5529,24 @@ subroutine json_get_by_path(json, me, path, p, found)
55245529
!! specify by `path`
55255530
logical(LK),intent(out),optional :: found !! true if it was found
55265531

5532+
character(kind=CK,len=max_integer_str_len),allocatable :: path_mode_str !! string version
5533+
!! of `json%path_mode`
5534+
55275535
nullify(p)
55285536

55295537
if (.not. json%exception_thrown) then
55305538

5531-
! note: it can only be 1 or 2 (which was checked in initialize)
5539+
! note: it can only be 1 or 2 (3 not currently enabled)
55325540
select case (json%path_mode)
55335541
case(1_IK)
55345542
call json%json_get_by_path_default(me, path, p, found)
55355543
case(2_IK)
55365544
call json%json_get_by_path_rfc6901(me, path, p, found)
5545+
case default
5546+
call integer_to_string(json%path_mode,int_fmt,path_mode_str)
5547+
call json%throw_exception('Error in json_get_by_path: Unsupported path_mode: '//&
5548+
trim(path_mode_str))
5549+
if (present(found)) found = .false.
55375550
end select
55385551

55395552
else
@@ -5572,6 +5585,8 @@ subroutine json_create_by_path(json,me,path,p,found,was_created)
55725585
!! (as opposed to already being there)
55735586

55745587
type(json_value),pointer :: tmp
5588+
character(kind=CK,len=max_integer_str_len),allocatable :: path_mode_str !! string version
5589+
!! of `json%path_mode`
55755590

55765591
if (present(p)) nullify(p)
55775592

@@ -5587,7 +5602,17 @@ subroutine json_create_by_path(json,me,path,p,found,was_created)
55875602
case(2_IK)
55885603
! the problem here is there isn't really a way to disambiguate
55895604
! the array elements, so '/a/0' could be 'a(1)' or 'a.0'.
5590-
call json%throw_exception('Create by path not supported in RFC 6901 path mode.')
5605+
call json%throw_exception('Error in json_create_by_path: '//&
5606+
'Create by path not supported in RFC 6901 path mode.')
5607+
if (present(found)) then
5608+
call json%clear_exceptions()
5609+
found = .false.
5610+
end if
5611+
if (present(was_created)) was_created = .false.
5612+
case default
5613+
call integer_to_string(json%path_mode,int_fmt,path_mode_str)
5614+
call json%throw_exception('Error in json_create_by_path: Unsupported path_mode: '//&
5615+
trim(path_mode_str))
55915616
if (present(found)) then
55925617
call json%clear_exceptions()
55935618
found = .false.
@@ -5640,6 +5665,8 @@ end subroutine wrap_json_create_by_path
56405665
!````
56415666
!
56425667
!### Notes
5668+
! The syntax used here is a subset of the
5669+
! [http://goessner.net/articles/JsonPath/](JSONPath) "dot–notation".
56435670
! The following special characters are used to denote paths:
56445671
!
56455672
! * `$` - root
@@ -5997,6 +6024,9 @@ end subroutine json_get_by_path_default
59976024
! (according to the standard, evaluation of non-unique references
59986025
! should fail). Like [[json_get_by_path_default]], this one will just return
59996026
! the first instance it encounters. This might be changed in the future.
6027+
!
6028+
!@warning I think the standard indicates that the input paths should use
6029+
! escaped JSON strings (currently we are assuming they are not escaped).
60006030

60016031
subroutine json_get_by_path_rfc6901(json, me, path, p, found)
60026032

@@ -6194,6 +6224,12 @@ end subroutine wrap_json_get_by_path
61946224
!
61956225
!@note If `json%path_mode/=1`, then the `use_alt_array_tokens`
61966226
! and `path_sep` inputs are ignored if present.
6227+
!
6228+
!@note [http://goessner.net/articles/JsonPath/](JSONPath) (`path_mode=3`)
6229+
! does not specify whether or not the keys should be escaped (this routine
6230+
! assumes not, as does http://jsonpath.com).
6231+
! Also, we are using Fortran-style 1-based array indices,
6232+
! not 0-based, to agree with the assumption in `path_mode=1`
61976233

61986234
subroutine json_get_path(json, p, path, found, use_alt_array_tokens, path_sep)
61996235

@@ -6205,15 +6241,18 @@ subroutine json_get_path(json, p, path, found, use_alt_array_tokens, path_sep)
62056241
logical(LK),intent(out),optional :: found !! true if there were no problems
62066242
logical(LK),intent(in),optional :: use_alt_array_tokens !! if true, then '()' are used for array elements
62076243
!! otherwise, '[]' are used [default]
6244+
!! (only used if `path_mode=1`)
62086245
character(kind=CK,len=1),intent(in),optional :: path_sep !! character to use for path separator
62096246
!! (otherwise use `json%path_separator`)
6247+
!! (only used if `path_mode=1`)
62106248

62116249
type(json_value),pointer :: tmp !! for traversing the structure
62126250
type(json_value),pointer :: element !! for traversing the structure
62136251
integer(IK) :: var_type !! JSON variable type flag
62146252
character(kind=CK,len=:),allocatable :: name !! variable name
62156253
character(kind=CK,len=:),allocatable :: parent_name !! variable's parent name
6216-
character(kind=CK,len=max_integer_str_len) :: istr !! for integer to string conversion (array indices)
6254+
character(kind=CK,len=max_integer_str_len) :: istr !! for integer to string conversion
6255+
!! (array indices)
62176256
integer(IK) :: i !! counter
62186257
integer(IK) :: n_children !! number of children for parent
62196258
logical(LK) :: use_brackets !! to use '[]' characters for arrays
@@ -6273,10 +6312,20 @@ subroutine json_get_path(json, p, path, found, use_alt_array_tokens, path_sep)
62736312
end if
62746313
end do
62756314
select case(json%path_mode)
6315+
case(3)
6316+
! JSONPath "bracket-notation"
6317+
! example: `$['key'][1]`
6318+
! [note: this uses 1-based indices]
6319+
call integer_to_string(i,int_fmt,istr)
6320+
call add_to_path(start_array//single_quote//parent_name//&
6321+
single_quote//end_array//&
6322+
start_array//trim(adjustl(istr))//end_array,CK_'')
62766323
case(2)
6324+
! rfc6901
62776325
call integer_to_string(i-1,int_fmt,istr) ! 0-based index
62786326
call add_to_path(parent_name//slash//trim(adjustl(istr)))
62796327
case(1)
6328+
! default
62806329
call integer_to_string(i,int_fmt,istr)
62816330
if (use_brackets) then
62826331
call add_to_path(parent_name//start_array//&
@@ -6291,7 +6340,13 @@ subroutine json_get_path(json, p, path, found, use_alt_array_tokens, path_sep)
62916340
case (json_object)
62926341

62936342
!process parent on the next pass
6294-
call add_to_path(name,path_sep)
6343+
select case(json%path_mode)
6344+
case(3)
6345+
call add_to_path(start_array//single_quote//name//&
6346+
single_quote//end_array,CK_'')
6347+
case default
6348+
call add_to_path(name,path_sep)
6349+
end select
62956350

62966351
case default
62976352

@@ -6305,7 +6360,13 @@ subroutine json_get_path(json, p, path, found, use_alt_array_tokens, path_sep)
63056360

63066361
else
63076362
!the last one:
6308-
call add_to_path(name,path_sep)
6363+
select case(json%path_mode)
6364+
case(3)
6365+
call add_to_path(start_array//single_quote//name//&
6366+
single_quote//end_array,CK_'')
6367+
case default
6368+
call add_to_path(name,path_sep)
6369+
end select
63096370
end if
63106371

63116372
if (associated(tmp%parent)) then
@@ -6328,10 +6389,14 @@ subroutine json_get_path(json, p, path, found, use_alt_array_tokens, path_sep)
63286389
if (json%exception_thrown .or. .not. allocated(path)) then
63296390
path = CK_''
63306391
else
6331-
if (json%path_mode==2) then
6392+
select case (json%path_mode)
6393+
case(3)
6394+
! add the outer level object identifier:
6395+
path = root//path
6396+
case(2)
63326397
! add the root slash:
63336398
path = slash//path
6334-
end if
6399+
end select
63356400
end if
63366401

63376402
!optional output:
@@ -6355,6 +6420,13 @@ subroutine add_to_path(str,path_sep)
63556420
!! (ignored if `json%path_mode/=1`)
63566421

63576422
select case (json%path_mode)
6423+
case(3)
6424+
! in this case, the options are ignored
6425+
if (.not. allocated(path)) then
6426+
path = str
6427+
else
6428+
path = str//path
6429+
end if
63586430
case(2)
63596431
! in this case, the options are ignored
63606432
if (.not. allocated(path)) then

src/tests/jf_test_1.f90

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ subroutine test_1(error_cnt)
9595
call core%initialize(unescape_strings=.true.)
9696
call core%traverse(p,print_json_variable)
9797

98+
namelist_style = .false.
99+
write(error_unit,'(A)') ''
100+
write(error_unit,'(A)') 'printing each variable [JSONPath style]'
101+
write(error_unit,'(A)') ''
102+
call core%initialize(path_mode=3)
103+
call core%traverse(p,print_json_variable)
104+
98105
! -------------------------
99106

100107
! extract data from the parsed value

0 commit comments

Comments
 (0)