Skip to content

Commit 7097bf0

Browse files
committed
added an optional allow_trailing_comma flag
can be used to disallow extra trailing commas in arrays and objects (it is true by default and thus allowed) Fixes #569
1 parent ebb2812 commit 7097bf0

File tree

6 files changed

+139
-9
lines changed

6 files changed

+139
-9
lines changed

fpm.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -261,4 +261,9 @@ main = "jf_test_49.F90"
261261
[[test]]
262262
name = "jf_test_50"
263263
source-dir = "src/tests"
264-
main = "jf_test_50.F90"
264+
main = "jf_test_50.F90"
265+
266+
[[test]]
267+
name = "jf_test_51"
268+
source-dir = "src/tests"
269+
main = "jf_test_51.F90"

src/json_initialize_arguments.inc

+3
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,7 @@ logical(LK),intent(in),optional :: strict_integer_type_checking
111111
!! * If true, an exception will be raised if the integer
112112
!! value cannot be read.
113113
!!
114+
!! (default is true)
115+
logical(LK),intent(in),optional :: allow_trailing_comma
116+
!! Allow a single trailing comma in arrays and objects.
114117
!! (default is true)

src/json_initialize_dummy_arguments.inc

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ stop_on_error,&
2222
null_to_real_mode,&
2323
non_normal_mode,&
2424
use_quiet_nan, &
25-
strict_integer_type_checking &
25+
strict_integer_type_checking, &
26+
allow_trailing_comma &

src/json_value_module.F90

+35-7
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ module json_value_module
287287
!! * If true [default], an exception will be raised if an integer
288288
!! value cannot be read when parsing JSON.
289289

290+
logical(LK) :: allow_trailing_comma = .true.
291+
!! Allow a single trailing comma in arrays and objects.
292+
290293
integer :: ichunk = 0 !! index in `chunk` for [[pop_char]]
291294
!! when `use_unformatted_stream=True`
292295
integer :: filesize = 0 !! the file size when when `use_unformatted_stream=True`
@@ -1140,6 +1143,10 @@ subroutine json_initialize(me,&
11401143
me%strict_integer_type_checking = strict_integer_type_checking
11411144
end if
11421145

1146+
if (present(allow_trailing_comma)) then
1147+
me%allow_trailing_comma = allow_trailing_comma
1148+
end if
1149+
11431150
!Set the format for real numbers:
11441151
! [if not changing it, then it remains the same]
11451152

@@ -10154,7 +10161,7 @@ recursive subroutine parse_value(json, unit, str, value)
1015410161

1015510162
! start object
1015610163
call json%to_object(value) !allocate class
10157-
call json%parse_object(unit, str, value)
10164+
call json%parse_object(unit, str, value, expecting_next_element=.false.)
1015810165

1015910166
case (start_array)
1016010167

@@ -10879,14 +10886,17 @@ end subroutine to_array
1087910886
!>
1088010887
! Core parsing routine.
1088110888

10882-
recursive subroutine parse_object(json, unit, str, parent)
10889+
recursive subroutine parse_object(json, unit, str, parent, expecting_next_element)
1088310890

1088410891
implicit none
1088510892

1088610893
class(json_core),intent(inout) :: json
1088710894
integer(IK),intent(in) :: unit !! file unit number (if parsing from a file)
1088810895
character(kind=CK,len=*),intent(in) :: str !! JSON string (if parsing from a string)
1088910896
type(json_value),pointer :: parent !! the parsed object will be added as a child of this
10897+
logical(LK),intent(in) :: expecting_next_element !! if true, this object is preceeded by a comma, so
10898+
!! we expect a valid object to exist. used to check
10899+
!! for trailing delimiters.
1089010900

1089110901
type(json_value),pointer :: pair !! temp variable
1089210902
logical(LK) :: eof !! end of file flag
@@ -10907,13 +10917,18 @@ recursive subroutine parse_object(json, unit, str, parent)
1090710917

1090810918
! pair name
1090910919
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
10910-
skip_comments=json%allow_comments, popped=c)
10920+
skip_comments=json%allow_comments, popped=c)
1091110921
if (eof) then
1091210922
call json%throw_exception('Error in parse_object:'//&
1091310923
' Unexpected end of file while parsing start of object.')
1091410924
return
1091510925
else if (end_object == c) then
1091610926
! end of an empty object
10927+
if (expecting_next_element .and. .not. json%allow_trailing_comma) then
10928+
! this is a dangling comma.
10929+
call json%throw_exception('Error in parse_object: '//&
10930+
'Dangling comma when parsing an object.')
10931+
end if
1091710932
return
1091810933
else if (quotation_mark == c) then
1091910934
call json_value_create(pair)
@@ -10935,7 +10950,7 @@ recursive subroutine parse_object(json, unit, str, parent)
1093510950

1093610951
! pair value
1093710952
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
10938-
skip_comments=json%allow_comments, popped=c)
10953+
skip_comments=json%allow_comments, popped=c)
1093910954
if (eof) then
1094010955
call json%destroy(pair)
1094110956
call json%throw_exception('Error in parse_object:'//&
@@ -10959,14 +10974,15 @@ recursive subroutine parse_object(json, unit, str, parent)
1095910974

1096010975
! another possible pair
1096110976
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
10962-
skip_comments=json%allow_comments, popped=c)
10977+
skip_comments=json%allow_comments, popped=c)
1096310978
if (eof) then
1096410979
call json%throw_exception('Error in parse_object: '//&
1096510980
'End of file encountered when parsing an object')
1096610981
return
1096710982
else if (delimiter == c) then
1096810983
! read the next member
10969-
call json%parse_object(unit = unit, str=str, parent = parent)
10984+
call json%parse_object(unit = unit, str=str, parent = parent, &
10985+
expecting_next_element=.true.)
1097010986
else if (end_object == c) then
1097110987
! end of object
1097210988
return
@@ -10996,6 +11012,9 @@ recursive subroutine parse_array(json, unit, str, array)
1099611012
type(json_value),pointer :: element !! temp variable for array element
1099711013
logical(LK) :: eof !! end of file flag
1099811014
character(kind=CK,len=1) :: c !! character returned by [[pop_char]]
11015+
logical(LK) :: expecting_next_element !! to check for trailing delimiters
11016+
11017+
expecting_next_element = .false.
1099911018

1100011019
do
1100111020

@@ -11011,7 +11030,10 @@ recursive subroutine parse_array(json, unit, str, array)
1101111030
end if
1101211031

1101311032
! parse value will deallocate an empty array value
11014-
if (associated(element)) call json%add(array, element)
11033+
if (associated(element)) then
11034+
expecting_next_element = .false.
11035+
call json%add(array, element)
11036+
end if
1101511037

1101611038
! popped the next character
1101711039
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
@@ -11024,9 +11046,15 @@ recursive subroutine parse_array(json, unit, str, array)
1102411046
exit
1102511047
else if (delimiter == c) then
1102611048
! parse the next element
11049+
expecting_next_element = .true.
1102711050
cycle
1102811051
else if (end_array == c) then
1102911052
! end of array
11053+
if (expecting_next_element .and. .not. json%allow_trailing_comma) then
11054+
! this is a dangling comma.
11055+
call json%throw_exception('Error in parse_array: '//&
11056+
'Dangling comma when parsing an array.')
11057+
end if
1103011058
exit
1103111059
else
1103211060
call json%throw_exception('Error in parse_array: '//&

src/tests/jf_test_51.F90

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
!*****************************************************************************************
2+
!>
3+
! Module for the 51th unit test. See Issue #569.
4+
5+
module jf_test_51_mod
6+
7+
use json_module, wp => json_RK, IK => json_IK, LK => json_LK, CK => json_CK
8+
use, intrinsic :: iso_fortran_env , only: error_unit, output_unit
9+
10+
implicit none
11+
12+
private
13+
public :: test_51
14+
15+
contains
16+
17+
subroutine test_51(error_cnt)
18+
19+
!! 51th unit test. see Issue #569
20+
21+
integer,intent(out) :: error_cnt
22+
23+
type(json_file) :: json
24+
25+
error_cnt = 0
26+
27+
write(error_unit,'(A)') ''
28+
write(error_unit,'(A)') '================================='
29+
write(error_unit,'(A)') ' EXAMPLE 51'
30+
write(error_unit,'(A)') '================================='
31+
write(error_unit,'(A)') ''
32+
33+
! array cases:
34+
call json%initialize(allow_trailing_comma = .false.)
35+
call json%deserialize('{"a" : [1,2,3,]}')
36+
if (.not. json%failed()) then
37+
error_cnt = error_cnt + 1
38+
write(error_unit,'(A)') 'read array with trailing comma'
39+
else
40+
! it was supposed to fail
41+
write(output_unit,'(A)') '1 success'
42+
end if
43+
44+
call json%initialize(allow_trailing_comma = .true.)
45+
call json%deserialize('{"a" : [1,2,3,]}')
46+
if (json%failed()) then
47+
error_cnt = error_cnt + 1
48+
write(error_unit,'(A)') 'failed to read array with trailing comma'
49+
else
50+
write(output_unit,'(A)') '2 success'
51+
end if
52+
53+
! object cases:
54+
call json%initialize(allow_trailing_comma = .false.)
55+
call json%deserialize('{"a" : 1, "b" : 2, }')
56+
if (.not. json%failed()) then
57+
error_cnt = error_cnt + 1
58+
write(error_unit,'(A)') 'read object with trailing comma'
59+
else
60+
! it was supposed to fail
61+
write(output_unit,'(A)') '3 success'
62+
end if
63+
call json%initialize(allow_trailing_comma = .true.)
64+
call json%deserialize('{"a" : 1, "b" : 2, }')
65+
if (json%failed()) then
66+
error_cnt = error_cnt + 1
67+
write(error_unit,'(A)') 'failed to read object with trailing comma'
68+
else
69+
write(output_unit,'(A)') '4 success'
70+
end if
71+
72+
end subroutine test_51
73+
74+
end module jf_test_51_mod
75+
!*****************************************************************************************
76+
77+
!*****************************************************************************************
78+
#ifndef INTEGRATED_TESTS
79+
program jf_test_51
80+
81+
use jf_test_51_mod , only: test_51
82+
83+
implicit none
84+
integer :: n_errors
85+
86+
call test_51(n_errors)
87+
if (n_errors /= 0) stop 1
88+
89+
end program jf_test_51
90+
#endif
91+
!*****************************************************************************************
92+

visual_studio/jsonfortrantest/jsonfortrantest.vfproj

+1
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,6 @@
9696
<File RelativePath="..\..\src\tests\jf_test_48.F90"/>
9797
<File RelativePath="..\..\src\tests\jf_test_49.F90"/>
9898
<File RelativePath="..\..\src\tests\jf_test_50.F90"/>
99+
<File RelativePath="..\..\src\tests\jf_test_51.F90"/>
99100
<File RelativePath=".\jsonfortrantest.f90"/></Filter></Files>
100101
<Globals/></VisualStudioProject>

0 commit comments

Comments
 (0)