Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
LEFEBVREJP email
radix
Commits
c56b5004
Commit
c56b5004
authored
Apr 02, 2020
by
Norby, Tom
Browse files
Template JSONParser; add tests.
parent
44a307f8
Pipeline
#95934
failed with stages
in 5 minutes and 17 seconds
Changes
4
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
radixcore/json.hh
View file @
c56b5004
...
...
@@ -8,13 +8,13 @@
#include
<sstream>
#include
<vector>
#include
"radixcore/value.hh"
#include
"radixcore/visibility.hh"
//-----------------------------------------------------------------------------
// see www.json.org for parsing grammar
namespace
radix
{
template
<
class
JsonType
>
class
RADIX_PUBLIC
JSONParser
{
public:
...
...
@@ -29,7 +29,7 @@ class RADIX_PUBLIC JSONParser
}
//-------------------------------------------------------------------------
Valu
e
&
root
()
{
return
m_root
;
}
JsonTyp
e
&
root
()
{
return
m_root
;
}
//-------------------------------------------------------------------------
std
::
string
last_error
()
...
...
@@ -136,7 +136,7 @@ class RADIX_PUBLIC JSONParser
}
//-------------------------------------------------------------------------
Valu
e
parse_array
()
JsonTyp
e
parse_array
()
{
if
(
m_po
>=
m_text
.
size
())
return
nullptr
;
if
(
m_text
[
m_po
]
!=
'['
)
return
nullptr
;
...
...
@@ -146,11 +146,11 @@ class RADIX_PUBLIC JSONParser
size_t
col_prev
=
m_col
;
bool
trailing_comma
=
false
;
Valu
e
parent
=
Valu
e
(
DataArray
());
JsonTyp
e
parent
=
JsonTyp
e
(
DataArray
());
for
(;
m_po
<
m_text
.
size
();
m_po
++
,
m_col
++
)
{
skip_whitespace
();
Valu
e
child
=
parse_value
();
JsonTyp
e
child
=
parse_value
();
if
(
child
.
is_null
())
{
if
(
m_last_error
!=
""
)
...
...
@@ -168,7 +168,7 @@ class RADIX_PUBLIC JSONParser
}
else
if
(
m_text
[
m_po
]
==
']'
)
break
;
return
Valu
e
();
return
JsonTyp
e
();
}
else
{
...
...
@@ -191,13 +191,13 @@ class RADIX_PUBLIC JSONParser
m_last_error
=
"invalid character '"
;
m_last_error
+=
ch
;
m_last_error
+=
"' in array"
;
return
Valu
e
();
return
JsonTyp
e
();
}
}
if
(
m_po
>=
m_text
.
size
()
||
m_text
[
m_po
]
!=
']'
)
{
m_last_error
=
"no closing bracket ']' for array"
;
return
Valu
e
();
return
JsonTyp
e
();
}
m_po
++
;
m_col
++
;
...
...
@@ -211,9 +211,9 @@ class RADIX_PUBLIC JSONParser
// (0|([1-9][0-9]*))
// (\.[0-9]+)?
// ([Ee][+-]?[0-9]+)?
Valu
e
parse_number
()
JsonTyp
e
parse_number
()
{
if
(
m_po
>=
m_text
.
size
())
return
Valu
e
();
if
(
m_po
>=
m_text
.
size
())
return
JsonTyp
e
();
size_t
len
=
0
;
...
...
@@ -226,7 +226,7 @@ class RADIX_PUBLIC JSONParser
if
(
m_po
>=
m_text
.
size
())
{
m_last_error
=
"invalid number (no digits after -)"
;
return
Valu
e
();
return
JsonTyp
e
();
}
}
...
...
@@ -235,7 +235,7 @@ class RADIX_PUBLIC JSONParser
if
(
m_po
>=
m_text
.
size
()
||
!
(
ch
>=
'0'
&&
m_text
[
m_po
]
<=
'9'
))
{
m_last_error
=
"invalid number (no digits)"
;
return
Valu
e
();
return
JsonTyp
e
();
}
m_po
++
;
m_col
++
;
...
...
@@ -254,12 +254,13 @@ class RADIX_PUBLIC JSONParser
{
try
{
Value
node
=
Value
(
std
::
stod
(
std
::
string
(
&
m_text
[
m_po
-
len
],
len
)));
JsonType
node
=
JsonType
(
std
::
stod
(
std
::
string
(
&
m_text
[
m_po
-
len
],
len
)));
return
node
;
}
catch
(...)
{
return
Valu
e
();
return
JsonTyp
e
();
}
}
ch
=
m_text
[
m_po
];
...
...
@@ -273,7 +274,7 @@ class RADIX_PUBLIC JSONParser
if
(
m_po
>=
m_text
.
size
())
{
m_last_error
=
"invalid number (no digits after decimal)"
;
return
Valu
e
();
return
JsonTyp
e
();
}
ch
=
m_text
[
m_po
];
size_t
n_digits
=
0
;
...
...
@@ -286,7 +287,7 @@ class RADIX_PUBLIC JSONParser
if
(
n_digits
==
0
)
{
m_last_error
=
"invalid number (no digits after decimal)"
;
return
Valu
e
();
return
JsonTyp
e
();
}
}
...
...
@@ -299,7 +300,7 @@ class RADIX_PUBLIC JSONParser
if
(
m_po
>=
m_text
.
size
())
{
m_last_error
=
"invalid number (no digits for exponent)"
;
return
Valu
e
();
return
JsonTyp
e
();
}
ch
=
m_text
[
m_po
];
// [+-]?
...
...
@@ -312,7 +313,7 @@ class RADIX_PUBLIC JSONParser
if
(
m_po
>=
m_text
.
size
())
{
m_last_error
=
"invalid number (no digits for exponent)"
;
return
Valu
e
();
return
JsonTyp
e
();
}
size_t
n_digits
=
0
;
...
...
@@ -325,18 +326,19 @@ class RADIX_PUBLIC JSONParser
if
(
n_digits
==
0
)
{
m_last_error
=
"invalid number (no digits after decimal)"
;
return
Valu
e
();
return
JsonTyp
e
();
}
}
try
{
Value
node
=
Value
(
std
::
stod
(
std
::
string
(
&
m_text
[
m_po
-
len
],
len
)));
JsonType
node
=
JsonType
(
std
::
stod
(
std
::
string
(
&
m_text
[
m_po
-
len
],
len
)));
return
node
;
}
catch
(...)
{
return
Valu
e
();
return
JsonTyp
e
();
}
}
...
...
@@ -357,7 +359,7 @@ class RADIX_PUBLIC JSONParser
// only 3 valid literals all in lower case: false, null, true
// TODO: need to refactor code to support null return (currently it is
// treated as an error)
Valu
e
parse_literal
()
JsonTyp
e
parse_literal
()
{
size_t
len
=
0
;
for
(;
m_po
+
len
<
m_text
.
size
();
len
++
)
...
...
@@ -373,32 +375,32 @@ class RADIX_PUBLIC JSONParser
m_col
+=
len
;
if
(
std
::
string
(
"true"
)
==
literals
[
i
])
{
return
Valu
e
(
true
);
return
JsonTyp
e
(
true
);
}
else
if
(
std
::
string
(
"false"
)
==
literals
[
i
])
{
return
Valu
e
(
false
);
return
JsonTyp
e
(
false
);
}
// default to null
return
Valu
e
();
return
JsonTyp
e
();
}
}
m_last_error
=
"invalid literal"
;
return
Valu
e
();
return
JsonTyp
e
();
}
//-------------------------------------------------------------------------
Valu
e
parse_object
()
JsonTyp
e
parse_object
()
{
if
(
m_po
>=
m_text
.
size
())
return
Valu
e
();
if
(
m_text
[
m_po
]
!=
'{'
)
return
Valu
e
();
if
(
m_po
>=
m_text
.
size
())
return
JsonTyp
e
();
if
(
m_text
[
m_po
]
!=
'{'
)
return
JsonTyp
e
();
m_po
++
;
m_col
++
;
size_t
line_prev
=
m_line
;
size_t
col_prev
=
m_col
;
bool
trailing_comma
=
false
;
Valu
e
parent
=
Valu
e
(
DataObject
());
JsonTyp
e
parent
=
JsonTyp
e
(
DataObject
());
for
(;
m_po
<
m_text
.
size
();
m_po
++
,
m_col
++
)
{
skip_whitespace
();
...
...
@@ -410,7 +412,7 @@ class RADIX_PUBLIC JSONParser
m_last_error
=
"trailing comma in object"
;
m_line
=
line_prev
;
m_col
=
col_prev
;
return
Valu
e
();
return
JsonTyp
e
();
}
break
;
}
...
...
@@ -425,7 +427,7 @@ class RADIX_PUBLIC JSONParser
m_line
=
line_prev
;
m_col
=
col_prev
;
}
return
Valu
e
();
return
JsonTyp
e
();
}
skip_whitespace
();
...
...
@@ -433,13 +435,13 @@ class RADIX_PUBLIC JSONParser
if
(
m_po
>=
m_text
.
size
()
||
m_text
[
m_po
]
!=
':'
)
{
m_last_error
=
"no ':' following key in object"
;
return
Valu
e
();
return
JsonTyp
e
();
}
m_po
++
;
m_col
++
;
// parse value
Valu
e
child
=
parse_value
();
JsonTyp
e
child
=
parse_value
();
if
(
child
.
is_null
())
{
if
(
m_last_error
==
""
)
...
...
@@ -452,7 +454,7 @@ class RADIX_PUBLIC JSONParser
m_line
=
line_prev
;
m_col
=
col_prev
;
}
return
Valu
e
();
return
JsonTyp
e
();
}
else
{
...
...
@@ -474,13 +476,13 @@ class RADIX_PUBLIC JSONParser
else
{
m_last_error
=
"invalid character in object"
;
return
Valu
e
();
return
JsonTyp
e
();
}
}
if
(
m_po
>=
m_text
.
size
()
||
m_text
[
m_po
]
!=
'}'
)
{
m_last_error
=
"no closing curly bracket '}' for object"
;
return
Valu
e
();
return
JsonTyp
e
();
}
m_po
++
;
m_col
++
;
...
...
@@ -584,19 +586,19 @@ class RADIX_PUBLIC JSONParser
}
//-------------------------------------------------------------------------
Valu
e
parse_string
()
JsonTyp
e
parse_string
()
{
std
::
string
str
=
parse_string_contents
();
if
(
m_last_error
!=
""
)
return
Valu
e
();
return
Valu
e
(
str
);
if
(
m_last_error
!=
""
)
return
JsonTyp
e
();
return
JsonTyp
e
(
str
);
}
//-------------------------------------------------------------------------
Valu
e
parse_value
()
JsonTyp
e
parse_value
()
{
skip_whitespace
();
char
ch
=
m_text
[
m_po
];
Valu
e
node
;
JsonTyp
e
node
;
if
(
ch
==
'"'
)
node
=
parse_string
();
else
if
(
ch
==
'['
)
...
...
@@ -617,7 +619,7 @@ class RADIX_PUBLIC JSONParser
const
char
*
literals
[
N_LITERALS
];
size_t
literal_lens
[
N_LITERALS
];
Valu
e
m_root
;
JsonTyp
e
m_root
;
std
::
string
m_text
=
""
;
...
...
radixcore/tests/CMakeLists.txt
View file @
c56b5004
INCLUDE
(
GoogleTest
)
ADD_GOOGLE_TEST
(
tstJson.cc NP 1
)
ADD_GOOGLE_TEST
(
tstString.cc NP 1
)
ADD_GOOGLE_TEST
(
tstSystem.cc NP 1
)
ADD_GOOGLE_TEST
(
tstValue.cc NP 1
)
configure_file
(
testfiles/wellformed.json testfiles/wellformed.json COPYONLY
)
radixcore/tests/testfiles/wellformed.json
0 → 100644
View file @
c56b5004
{
"root object"
:
{
"bool"
:
true
,
"int"
:
1
,
"double"
:
2.34
,
"string"
:
"This is a string!"
,
"array"
:
[
"Array of JSON values."
,
1
,
2.34
,
true
]
}
}
\ No newline at end of file
radixcore/tests/tstJson.cc
0 → 100644
View file @
c56b5004
#include
"gtest/gtest.h"
#include
"radixcore/json.hh"
#include
"radixcore/value.hh"
#include
<QDir>
using
namespace
radix
;
// JsonType& root() { return m_root; }
// std::string last_error()
// bool parse()
// bool parse_from_stream(std::istream& in_stream)
// bool parse_from_file(std::string fn)
TEST
(
JSONParser
,
parse_from_file
)
{
QDir
workingDir
(
"."
);
{
JSONParser
<
Value
>
sunnyDay
;
std
::
string
filename
=
workingDir
.
absoluteFilePath
(
"testfiles/wellformed.json"
).
toStdString
();
bool
success
=
sunnyDay
.
parse_from_file
(
filename
);
EXPECT_TRUE
(
success
);
// not really an error just the null terminating at eof
std
::
string
errorMsg
=
sunnyDay
.
last_error
();
EXPECT_EQ
(
" at line 14 column 2"
,
errorMsg
);
Value
root
=
sunnyDay
.
root
();
Value
rootObj
=
root
[
"root object"
];
EXPECT_EQ
(
Value
::
TYPE_OBJECT
,
rootObj
.
type
());
EXPECT_EQ
(
Value
::
TYPE_BOOLEAN
,
rootObj
[
"bool"
].
type
());
EXPECT_TRUE
(
rootObj
[
"bool"
].
to_bool
());
// reads in as double, not int.
// EXPECT_EQ(Value::TYPE_INTEGER, rootObj["int"].type());
EXPECT_EQ
(
1
,
rootObj
[
"int"
].
to_int
());
EXPECT_EQ
(
Value
::
TYPE_DOUBLE
,
rootObj
[
"double"
].
type
());
EXPECT_EQ
(
2.34
,
rootObj
[
"double"
].
to_double
());
EXPECT_EQ
(
Value
::
TYPE_STRING
,
rootObj
[
"string"
].
type
());
EXPECT_EQ
(
"This is a string!"
,
rootObj
[
"string"
].
to_string
());
EXPECT_EQ
(
Value
::
TYPE_ARRAY
,
rootObj
[
"array"
].
type
());
DataArray
&
array
=
rootObj
[
"array"
].
as_array
();
EXPECT_EQ
(
"Array of JSON values."
,
array
.
at
(
0
).
to_string
());
EXPECT_EQ
(
1
,
array
.
at
(
1
).
to_int
());
EXPECT_EQ
(
2.34
,
array
.
at
(
2
).
to_double
());
EXPECT_TRUE
(
array
.
at
(
3
).
to_bool
());
}
{
JSONParser
<
Value
>
noFile
;
std
::
string
filename
=
workingDir
.
absoluteFilePath
(
"wrong_path/file_doesn't_exist.json"
)
.
toStdString
();
bool
success
=
noFile
.
parse_from_file
(
filename
);
EXPECT_FALSE
(
success
);
std
::
string
errorMsg
=
noFile
.
last_error
();
EXPECT_EQ
(
"could not open file at line 1 column 1"
,
errorMsg
);
}
}
TEST
(
JSONParser
,
parse_from_stream
)
{
// check code paths in JSONParser class
{
JSONParser
<
Value
>
jp
;
std
::
string
errorMsg
=
""
;
// trailing junk
std
::
stringstream
ss
(
"{}
\r\n
foo"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"unexpected trailing character(s) at line 2 column 1"
,
errorMsg
);
// unclosed array with no child elements
ss
.
str
(
"["
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"no closing bracket ']' for array at line 1 column 2"
,
errorMsg
);
// unclosed array with at least one child element
ss
.
str
(
"[
\"
bar
\"
"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
std
::
string
expected
(
"invalid character '
\0
' in array at line 1 column 8"
,
49
);
EXPECT_EQ
(
expected
,
errorMsg
);
// trailing comma in array
ss
.
str
(
"[
\"
baz
\"
, ]"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"trailing comma in array at line 1 column 9"
,
errorMsg
);
// lone '-' at end (considered as invalid number)
ss
.
str
(
"-"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid number (no digits after -) at line 1 column 2"
,
errorMsg
);
// lone '-' followed by whitespace (considered as invalid number)
ss
.
str
(
"- "
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid number (no digits) at line 1 column 2"
,
errorMsg
);
// no digits after decimal
ss
.
str
(
"5."
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid number (no digits after decimal) at line 1 column 3"
,
errorMsg
);
// no digits after decimal
ss
.
str
(
"5. "
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid number (no digits after decimal) at line 1 column 3"
,
errorMsg
);
// no digits after 'e' in exponent at end
ss
.
str
(
"1.2e"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid number (no digits for exponent) at line 1 column 5"
,
errorMsg
);
// no digits after 'e' in exponent at end
ss
.
str
(
"1.2e-"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid number (no digits for exponent) at line 1 column 6"
,
errorMsg
);
// no digits after 'e' in exponent followed by whitespace
ss
.
str
(
"3.4E "
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid number (no digits after decimal) at line 1 column 5"
,
errorMsg
);
// invalid literal
ss
.
str
(
"foo"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid literal at line 1 column 1"
,
errorMsg
);
// null literal
ss
.
str
(
"null"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
" at line 1 column 5"
,
errorMsg
);
// unclosed object with no child elements
ss
.
str
(
"{"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"no closing curly bracket '}' for object at line 1 column 2"
,
errorMsg
);
// object child with bad key
ss
.
str
(
"{
\"
bar"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"string missing closing quote at line 1 column 7"
,
errorMsg
);
// unclosed object with at least one child element
ss
.
str
(
"{
\"
bar
\"
: 1"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid character in object at line 1 column 12"
,
errorMsg
);
// trailing comma in object
ss
.
str
(
"{
\"
baz
\"
: 1 , }"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"trailing comma in object at line 1 column 13"
,
errorMsg
);
// missing value in object
ss
.
str
(
"{
\"
foo
\"
: }"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"missing value in object at line 1 column 11"
,
errorMsg
);
// missing ':' and value in object
ss
.
str
(
"{
\"
foo
\"
}"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"no ':' following key in object at line 1 column 9"
,
errorMsg
);
// invalid escape sequence
ss
.
str
(
"
\"\\
"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"incomplete unicode character escape sequence in string at line 1 "
"column 3"
,
errorMsg
);
// invalid escape sequence
ss
.
str
(
"
\"\\
q"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid escape sequence in string at line 1 column 3"
,
errorMsg
);
// incomplete unicode escape sequence
ss
.
str
(
"
\"\\
u012"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"incomplete unicode character escape sequence in string at line 1 "
"column 7"
,
errorMsg
);
// invalid unicode escape sequence
ss
.
str
(
"
\"\\
u012Q
\"
"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"invalid unicode character escape sequence in string at line 1 column "
"7"
,
errorMsg
);
// non-terminated string
ss
.
str
(
"
\"
bar"
);
EXPECT_FALSE
(
jp
.
parse_from_stream
(
ss
));
errorMsg
=
jp
.
last_error
();
EXPECT_EQ
(
"string missing closing quote at line 1 column 5"
,
errorMsg
);
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment