Skip to content

Commit 6345937

Browse files
committed
Added a new allow_duplicate_keys option to automatically check for duplicate keys when parsing or validating.
1 parent 346852f commit 6345937

File tree

4 files changed

+157
-13
lines changed

4 files changed

+157
-13
lines changed

src/json_file_module.F90

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@ subroutine initialize_json_core_in_file(me,verbose,compact_reals,&
338338
comment_char,&
339339
path_mode,&
340340
path_separator,&
341-
compress_vectors)
341+
compress_vectors,&
342+
allow_duplicate_keys)
342343

343344
implicit none
344345

@@ -355,7 +356,8 @@ subroutine initialize_json_core_in_file(me,verbose,compact_reals,&
355356
comment_char,&
356357
path_mode,&
357358
path_separator,&
358-
compress_vectors)
359+
compress_vectors,&
360+
allow_duplicate_keys)
359361

360362
end subroutine initialize_json_core_in_file
361363
!*****************************************************************************************
@@ -419,7 +421,8 @@ function initialize_json_file(p,verbose,compact_reals,&
419421
comment_char,&
420422
path_mode,&
421423
path_separator,&
422-
compress_vectors) result(file_object)
424+
compress_vectors,&
425+
allow_duplicate_keys) result(file_object)
423426

424427
implicit none
425428

@@ -438,7 +441,8 @@ function initialize_json_file(p,verbose,compact_reals,&
438441
comment_char,&
439442
path_mode,&
440443
path_separator,&
441-
compress_vectors)
444+
compress_vectors,&
445+
allow_duplicate_keys)
442446

443447
if (present(p)) file_object%p => p
444448

src/json_initialize_arguments.inc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,10 @@ logical(LK),intent(in),optional :: compress_vectors
5959
!! [Note: `no_whitespace` will
6060
!! override this option if necessary].
6161
!! (Default is False).
62+
logical(LK),intent(in),optional :: allow_duplicate_keys
63+
!! * If True [default] then no special checks
64+
!! are done to check for duplicate keys.
65+
!! * If False, then after parsing, if any duplicate
66+
!! keys are found, an error is thrown. A call to
67+
!! [[json_value_validate]] will also check for
68+
!! duplicates.

src/json_value_module.F90

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ module json_value_module
229229
!! [Note: `no_whitespace` will
230230
!! override this option if necessary]
231231

232+
logical(LK) :: allow_duplicate_keys = .true. !! If False, then after parsing, if any
233+
!! duplicate keys are found, an error is
234+
!! thrown. A call to [[json_value_validate]]
235+
!! will also check for duplicates. If True
236+
!! [default] then no special checks are done
237+
232238
contains
233239

234240
private
@@ -781,7 +787,8 @@ function initialize_json_core(verbose,compact_reals,&
781787
comment_char,&
782788
path_mode,&
783789
path_separator,&
784-
compress_vectors) result(json_core_object)
790+
compress_vectors,&
791+
allow_duplicate_keys) result(json_core_object)
785792

786793
implicit none
787794

@@ -798,7 +805,8 @@ function initialize_json_core(verbose,compact_reals,&
798805
comment_char,&
799806
path_mode,&
800807
path_separator,&
801-
compress_vectors)
808+
compress_vectors,&
809+
allow_duplicate_keys)
802810

803811
end function initialize_json_core
804812
!*****************************************************************************************
@@ -832,7 +840,8 @@ subroutine json_initialize(me,verbose,compact_reals,&
832840
comment_char,&
833841
path_mode,&
834842
path_separator,&
835-
compress_vectors)
843+
compress_vectors,&
844+
allow_duplicate_keys)
836845

837846
implicit none
838847

@@ -904,6 +913,11 @@ subroutine json_initialize(me,verbose,compact_reals,&
904913
me%compress_vectors = compress_vectors
905914
end if
906915

916+
! checking for duplicate keys:
917+
if (present(allow_duplicate_keys)) then
918+
me%allow_duplicate_keys = allow_duplicate_keys
919+
end if
920+
907921
!Set the format for real numbers:
908922
! [if not changing it, then it remains the same]
909923

@@ -2372,21 +2386,77 @@ end function json_value_is_child_of
23722386
!
23732387
! It recursively traverses the entire structure and checks every element.
23742388
!
2389+
!### History
2390+
! * Jacob Williams, 8/26/2017 : added duplicate key check.
2391+
!
2392+
!@note It will return on the first error it encounters.
2393+
!
23752394
!@note This routine does not check or throw any exceptions.
2395+
! If `json` is currently in a state of exception, it will
2396+
! remain so after calling this routine.
23762397

23772398
subroutine json_value_validate(json,p,is_valid,error_msg)
23782399

23792400
implicit none
23802401

2381-
class(json_core),intent(inout) :: json
2382-
type(json_value),pointer,intent(in) :: p
2383-
logical(LK),intent(out) :: is_valid !! True if the structure is valid.
2402+
class(json_core),intent(inout) :: json
2403+
type(json_value),pointer,intent(in) :: p
2404+
logical(LK),intent(out) :: is_valid !! True if the structure is valid.
23842405
character(kind=CK,len=:),allocatable,intent(out) :: error_msg !! if not valid, this will contain
23852406
!! a description of the problem
23862407

2408+
logical(LK) :: has_duplicate !! to check for duplicate keys
2409+
character(kind=CK,len=:),allocatable :: path !! path to duplicate key
2410+
logical(LK) :: status_ok !! to check for existing exception
2411+
logical(LK) :: status_ok2 !! to check for a new exception
2412+
character(kind=CK,len=:),allocatable :: exception_msg !! error message for an existing exception
2413+
character(kind=CK,len=:),allocatable :: exception_msg2 !! error message for a new exception
2414+
23872415
if (associated(p)) then
2416+
23882417
is_valid = .true.
23892418
call check_if_valid(p,require_parent=associated(p%parent))
2419+
2420+
if (is_valid .and. .not. json%allow_duplicate_keys) then
2421+
! if no errors so far, also check the
2422+
! entire structure for duplicate keys:
2423+
2424+
! note: check_for_duplicate_keys does call routines
2425+
! that check and throw exceptions, so let's clear any
2426+
! first. (save message for later)
2427+
call json%check_for_errors(status_ok, exception_msg)
2428+
call json%clear_exceptions()
2429+
2430+
call json%check_for_duplicate_keys(p,has_duplicate,path=path)
2431+
if (json%failed()) then
2432+
! if an exception was thrown during this call,
2433+
! then clear it but make that the error message
2434+
! returned by this routine. Normally this should
2435+
! never actually occur since we have already
2436+
! validated the structure.
2437+
call json%check_for_errors(is_valid, exception_msg2)
2438+
error_msg = exception_msg2
2439+
call json%clear_exceptions()
2440+
is_valid = .false.
2441+
else
2442+
if (has_duplicate) then
2443+
error_msg = 'duplicate key found: '//path
2444+
is_valid = .false.
2445+
end if
2446+
end if
2447+
2448+
if (.not. status_ok) then
2449+
! restore any existing exception if necessary
2450+
call json%throw_exception(exception_msg)
2451+
end if
2452+
2453+
! cleanup:
2454+
if (allocated(path)) deallocate(path)
2455+
if (allocated(exception_msg)) deallocate(exception_msg)
2456+
if (allocated(exception_msg2)) deallocate(exception_msg2)
2457+
2458+
end if
2459+
23902460
else
23912461
error_msg = 'The pointer is not associated'
23922462
is_valid = .false.
@@ -7726,6 +7796,8 @@ subroutine json_parse_file(json, file, p, unit)
77267796
integer(IK) :: iunit !! file unit actually used
77277797
integer(IK) :: istat !! iostat flag
77287798
logical(LK) :: is_open !! if the file is already open
7799+
logical(LK) :: has_duplicate !! if checking for duplicate keys
7800+
character(kind=CDK,len=:),allocatable :: path !! path to any duplicate key
77297801

77307802
!clear any exceptions and initialize:
77317803
call json%initialize()
@@ -7782,11 +7854,25 @@ subroutine json_parse_file(json, file, p, unit)
77827854

77837855
! parse as a value
77847856
call json%parse_value(unit=iunit, str=CK_'', value=p)
7785-
if (json%exception_thrown) call json%annotate_invalid_json(iunit,CK_'')
77867857

77877858
! close the file if necessary
77887859
close(unit=iunit, iostat=istat)
77897860

7861+
! check for errors:
7862+
if (json%exception_thrown) then
7863+
call json%annotate_invalid_json(iunit,CK_'')
7864+
else
7865+
if (.not. json%allow_duplicate_keys) then
7866+
call json%check_for_duplicate_keys(p,has_duplicate,path=path)
7867+
if (.not. json%exception_thrown) then
7868+
if (has_duplicate) then
7869+
call json%throw_exception('Error in json_parse_file: '//&
7870+
'Duplicate key found: '//path)
7871+
end if
7872+
end if
7873+
end if
7874+
end if
7875+
77907876
else
77917877

77927878
call json%throw_exception('Error in json_parse_file: Error opening file: '//trim(file))
@@ -7814,6 +7900,9 @@ subroutine json_parse_string(json, p, str)
78147900

78157901
integer(IK),parameter :: iunit = 0 !! indicates that json data will be read from buffer
78167902

7903+
logical(LK) :: has_duplicate !! if checking for duplicate keys
7904+
character(kind=CDK,len=:),allocatable :: path !! path to any duplicate key
7905+
78177906
!clear any exceptions and initialize:
78187907
call json%initialize()
78197908

@@ -7827,7 +7916,19 @@ subroutine json_parse_string(json, p, str)
78277916
! parse as a value
78287917
call json%parse_value(unit=iunit, str=str, value=p)
78297918

7830-
if (json%exception_thrown) call json%annotate_invalid_json(iunit,str)
7919+
if (json%exception_thrown) then
7920+
call json%annotate_invalid_json(iunit,str)
7921+
else
7922+
if (.not. json%allow_duplicate_keys) then
7923+
call json%check_for_duplicate_keys(p,has_duplicate,path=path)
7924+
if (.not. json%exception_thrown) then
7925+
if (has_duplicate) then
7926+
call json%throw_exception('Error in json_parse_string: '//&
7927+
'Duplicate key found: '//path)
7928+
end if
7929+
end if
7930+
end if
7931+
end if
78317932

78327933
end subroutine json_parse_string
78337934
!*****************************************************************************************

src/tests/jf_test_29.f90

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ subroutine test(json_str,correct_has_duplicate,correct_name,correct_path)
5252
logical(LK) :: has_duplicate
5353
character(kind=CK,len=:),allocatable :: name
5454
character(kind=CK,len=:),allocatable :: path
55+
logical(LK) :: is_valid
56+
character(kind=CK,len=:),allocatable :: error_msg
5557

5658
call json%initialize(no_whitespace=.true.)
5759

@@ -96,10 +98,40 @@ subroutine test(json_str,correct_has_duplicate,correct_name,correct_path)
9698
end if
9799

98100
end if
99-
100101
call json%destroy(p)
101102
call json%destroy()
102103

104+
! also check using two other methods:
105+
if (error_cnt==0) then
106+
107+
! check when the string is parsed:
108+
call json%initialize(allow_duplicate_keys=.false.)
109+
call json%parse(p,json_str)
110+
if (json%failed() .eqv. correct_has_duplicate) then
111+
write(output_unit,'(A)') ' Test passed: parse'
112+
else
113+
write(output_unit,'(A)') ' Test failed: parse'
114+
error_cnt = error_cnt + 1
115+
end if
116+
call json%destroy(p)
117+
call json%destroy()
118+
119+
! check by explicit call to validate:
120+
call json%initialize() ! don't throw an exception when parsing
121+
call json%parse(p,json_str)
122+
call json%initialize(allow_duplicate_keys=.false.)
123+
call json%validate(p,is_valid,error_msg)
124+
if (is_valid .eqv. (.not. correct_has_duplicate)) then
125+
write(output_unit,'(A)') ' Test passed: validate'
126+
else
127+
write(output_unit,'(A)') ' Test failed: validate'
128+
error_cnt = error_cnt + 1
129+
end if
130+
call json%destroy(p)
131+
call json%destroy()
132+
133+
end if
134+
103135
end subroutine test
104136

105137
end subroutine test_29

0 commit comments

Comments
 (0)