json print/parse from/to PVStructure

This commit is contained in:
Michael Davidsaver
2017-07-13 18:02:10 +02:00
parent 918b7f96db
commit ee4fdf3f39
12 changed files with 1169 additions and 0 deletions

View File

@@ -767,6 +767,7 @@ INPUT = ../src/pv \
./mainpage.h \
../src/copy/pv \
../src/misc/pv \
../src/json/pv \
./release_notes.h
# This tag can be used to specify the character encoding of the source files

View File

@@ -11,6 +11,7 @@ include $(PVDATA_SRC)/factory/Makefile
include $(PVDATA_SRC)/property/Makefile
include $(PVDATA_SRC)/copy/Makefile
include $(PVDATA_SRC)/pvMisc/Makefile
include $(PVDATA_SRC)/json/Makefile
LIBRARY = pvData

10
src/json/Makefile Normal file
View File

@@ -0,0 +1,10 @@
# This is a Makefile fragment, see ../Makefile
SRC_DIRS += $(PVDATA_SRC)/json
INC += pv/json.h
LIBSRCS += parsehelper.cpp
LIBSRCS += parseany.cpp
LIBSRCS += parseinto.cpp
LIBSRCS += print.cpp

276
src/json/parseany.cpp Normal file
View File

@@ -0,0 +1,276 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <sstream>
#include <pv/pvdVersion.h>
#include <pv/pvData.h>
#include <pv/valueBuilder.h>
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
#include <yajl_parse.h>
#define epicsExportSharedSymbols
#include "pv/json.h"
namespace pvd = epics::pvData;
namespace {
struct context {
unsigned depth;
enum state_t {
Undefined,
Key,
Array,
} state;
pvd::shared_vector<void> arr;
pvd::ValueBuilder root,
*cur;
std::string msg,
key;
context() :depth(0u), state(Undefined), cur(&root) {}
};
#define TRY context *self = (context*)ctx; try
#define CATCH() catch(std::exception& e) { self->msg = e.what(); return 0; }
int jtree_null(void * ctx)
{
TRY {
self->msg = "NULL value not permitted";
return 0;
}CATCH()
}
int jtree_boolean(void * ctx, int boolVal)
{
TRY {
if(self->depth==0) throw std::runtime_error("Bare value not supported");
switch(self->state) {
case context::Key:
self->cur = &self->cur->add<pvd::pvBoolean>(self->key, boolVal);
self->key.clear();
self->state = context::Undefined;
break;
case context::Array:
{
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvBoolean)
throw std::runtime_error("Mixed type array not supported");
pvd::shared_vector<pvd::boolean> arr(pvd::static_shared_vector_cast<pvd::boolean>(self->arr));
arr.push_back(boolVal);
self->arr = pvd::static_shared_vector_cast<void>(arr);
break;
}
default:
throw std::logic_error("boolean in bad state");
}
return 1;
}CATCH()
}
int jtree_integer(void * ctx, long integerVal)
{
TRY {
if(self->depth==0) throw std::runtime_error("Bare value not supported");
switch(self->state) {
case context::Key:
self->cur = &self->cur->add<pvd::pvLong>(self->key, integerVal);
self->key.clear();
self->state = context::Undefined;
break;
case context::Array:
{
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvLong)
throw std::runtime_error("Mixed type array not supported");
pvd::shared_vector<pvd::int64> arr(pvd::static_shared_vector_cast<pvd::int64>(self->arr));
arr.push_back(integerVal);
self->arr = pvd::static_shared_vector_cast<void>(arr);
break;
}
default:
throw std::logic_error("int64 in bad state");
}
return 1;
}CATCH()
}
int jtree_double(void * ctx, double doubleVal)
{
TRY {
if(self->depth==0) throw std::runtime_error("Bare value not supported");
switch(self->state) {
case context::Key:
self->cur = &self->cur->add<pvd::pvDouble>(self->key, doubleVal);
self->key.clear();
self->state = context::Undefined;
break;
case context::Array:
{
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvDouble)
throw std::runtime_error("Mixed type array not supported");
pvd::shared_vector<double> arr(pvd::static_shared_vector_cast<double>(self->arr));
arr.push_back(doubleVal);
self->arr = pvd::static_shared_vector_cast<void>(arr);
break;
}
default:
throw std::logic_error("double in bad state");
}
return 1;
}CATCH()
}
int jtree_string(void * ctx, const unsigned char * stringVal,
unsigned int stringLen)
{
TRY {
if(self->depth==0) throw std::runtime_error("Bare value not supported");
std::string sval((const char*)stringVal, stringLen);
switch(self->state) {
case context::Key:
self->cur = &self->cur->add<pvd::pvString>(self->key, sval);
self->key.clear();
self->state = context::Undefined;
break;
case context::Array:
{
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvString)
throw std::runtime_error("Mixed type array not supported");
pvd::shared_vector<std::string> arr(pvd::static_shared_vector_cast<std::string>(self->arr));
arr.push_back(sval);
self->arr = pvd::static_shared_vector_cast<void>(arr);
break;
}
default:
throw std::logic_error("double in bad state");
}
return 1;
}CATCH()
}
int jtree_start_map(void * ctx)
{
TRY {
if(self->depth>0) {
if(self->key.empty())
throw std::logic_error("anonymous dict not top level?");
self->cur = &self->cur->addNested(self->key);
self->key.clear();
}
self->depth++;
return 1;
}CATCH()
}
int jtree_map_key(void * ctx, const unsigned char * key,
unsigned int stringLen)
{
TRY {
if(!self->key.empty())
throw std::logic_error("double key?");
if(stringLen==0)
throw std::runtime_error("empty key not allowed");
self->key = std::string((const char*)key, stringLen);
self->state = context::Key;
return 1;
}CATCH()
}
int jtree_end_map(void * ctx)
{
TRY {
if(self->depth>1)
self->cur = &self->cur->endNested();
else if(self->depth==0)
throw std::logic_error("Unbalenced dict");
self->depth--;
return 1;
}CATCH()
}
int jtree_start_array(void * ctx)
{
TRY {
if(self->depth==0) throw std::runtime_error("Bare array not supported");
if(self->state!=context::Key)
throw std::logic_error("bare array not supported");
self->state = context::Array;
return 1;
}CATCH()
}
int jtree_end_array(void * ctx)
{
TRY {
if(self->state!=context::Array)
throw std::logic_error("Bad array parse");
self->cur = &self->cur->add(self->key, pvd::freeze(self->arr));
self->key.clear();
self->state = context::Undefined;
return 1;
}CATCH()
}
yajl_callbacks jtree_cbs = {
&jtree_null,
&jtree_boolean,
&jtree_integer,
&jtree_double,
NULL, // number
&jtree_string,
&jtree_start_map,
&jtree_map_key,
&jtree_end_map,
&jtree_start_array,
&jtree_end_array,
};
struct handler {
yajl_handle handle;
handler(yajl_handle handle) :handle(handle)
{
if(!handle)
throw std::runtime_error("Failed to allocate yajl handle");
}
~handler() {
yajl_free(handle);
}
operator yajl_handle() { return handle; }
};
} // namespace
namespace epics{namespace pvData{
epics::pvData::PVStructure::shared_pointer
parseJSON(std::istream& strm)
{
yajl_parser_config conf = {
.allowComments = 1,
.checkUTF8 = 1,
};
context ctxt;
handler handle(yajl_alloc(&jtree_cbs, &conf, NULL, &ctxt));
if(!yajl_parse_helper(strm, handle, conf))
throw std::runtime_error(ctxt.msg);
return ctxt.cur->buildPVStructure();
}
}} // namespace epics::pvData
#endif // EPICS_VERSION_INT

104
src/json/parsehelper.cpp Normal file
View File

@@ -0,0 +1,104 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <stdexcept>
#include <sstream>
#include <pv/pvdVersion.h>
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
#define epicsExportSharedSymbols
#include "pv/json.h"
namespace {
void check_trailing(const std::string& line, bool commentok)
{
size_t idx = line.find_first_not_of(" \t\n\r");
if(idx==line.npos) return;
// TODO: detect the end of potentially multi-line comments...
// for now trailing comments not allowed
throw std::runtime_error("Trailing junk");
}
} // namespace
namespace epics{namespace pvData{
bool yajl_parse_helper(std::istream& src,
yajl_handle handle,
const yajl_parser_config& config)
{
unsigned linenum=0;
bool done = false;
std::string line;
while(std::getline(src, line)) {
linenum++;
if(done) {
check_trailing(line, config.allowComments);
continue;
}
yajl_status sts = yajl_parse(handle, (const unsigned char*)line.c_str(), line.size());
switch(sts) {
case yajl_status_ok: {
size_t consumed = yajl_get_bytes_consumed(handle);
if(consumed<line.size()) {
check_trailing(line.substr(consumed), config.allowComments);
}
done = true;
break;
}
case yajl_status_client_canceled:
return false;
case yajl_status_insufficient_data:
// continue with next line
break;
case yajl_status_error:
{
std::ostringstream msg;
unsigned char *raw = yajl_get_error(handle, 1, (const unsigned char*)line.c_str(), line.size());
if(!raw) {
msg<<"Unknown error on line "<<linenum;
} else {
try {
msg<<"Error on line "<<linenum<<" : "<<(const char*)raw;
}catch(...){
yajl_free_error(handle, raw);
throw;
}
yajl_free_error(handle, raw);
}
throw std::runtime_error(msg.str());
}
}
}
if(!src.eof() || src.bad()) {
std::ostringstream msg;
msg<<"I/O error after line "<<linenum;
throw std::runtime_error(msg.str());
} else if(!done) switch(yajl_parse_complete(handle)) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
return false;
case yajl_status_insufficient_data:
throw std::runtime_error("unexpected end of input");
case yajl_status_error:
throw std::runtime_error("Error while completing parsing");
}
return true;
}
}} // namespace epics::pvData
#endif // EPICS_VERSION_INT

260
src/json/parseinto.cpp Normal file
View File

@@ -0,0 +1,260 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <vector>
#include <sstream>
#include <pv/pvdVersion.h>
#include <pv/pvData.h>
#include <pv/valueBuilder.h>
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
#include <yajl_parse.h>
#define epicsExportSharedSymbols
#include "pv/json.h"
namespace pvd = epics::pvData;
namespace {
struct context {
std::string msg;
typedef std::vector<pvd::PVFieldPtr> stack_t;
stack_t stack;
context(const pvd::PVFieldPtr& root)
{
stack.push_back(root);
}
};
#define TRY context *self = (context*)ctx; assert(!self->stack.empty()); try
#define CATCH() catch(std::exception& e) { self->msg = e.what(); return 0; }
int jtree_null(void * ctx)
{
TRY {
self->msg = "NULL value not permitted";
return 0;
}CATCH()
}
template<typename PVD>
void valueAssign(context *self, typename PVD::value_type val)
{
pvd::Type type(self->stack.back()->getField()->getType());
if(type==pvd::scalar) {
pvd::PVScalar* fld(static_cast<pvd::PVScalar*>(self->stack.back().get()));
if(!fld)
throw std::invalid_argument("Not a scalar field");
fld->putFrom(val);
self->stack.pop_back();
// structure back at the top of the stack
} else if(type==pvd::scalarArray) {
pvd::PVScalarArray* fld(static_cast<pvd::PVScalarArray*>(self->stack.back().get()));
PVD* arrfld(dynamic_cast<PVD*>(fld));
if(!arrfld)
throw std::invalid_argument("wrong type for scalar array");
typename PVD::const_svector carr;
arrfld->swap(carr);
typename PVD::svector arr(pvd::thaw(carr));
arr.push_back(val);
arrfld->replace(pvd::freeze(arr));
// leave array field at top of stack
} else {
throw std::invalid_argument("Can't assign value");
}
}
int jtree_boolean(void * ctx, int boolVal)
{
TRY {
valueAssign<pvd::PVBooleanArray>(self, !!boolVal);
return 1;
}CATCH()
}
int jtree_integer(void * ctx, long integerVal)
{
TRY {
valueAssign<pvd::PVLongArray>(self, integerVal);
return 1;
}CATCH()
}
int jtree_double(void * ctx, double doubleVal)
{
TRY {
valueAssign<pvd::PVDoubleArray>(self, doubleVal);
return 1;
}CATCH()
}
int jtree_string(void * ctx, const unsigned char * stringVal,
unsigned int stringLen)
{
TRY {
std::string val((const char*)stringVal, stringLen);
valueAssign<pvd::PVStringArray>(self, val);
return 1;
}CATCH()
}
int jtree_start_map(void * ctx)
{
TRY {
pvd::PVFieldPtr& back(self->stack.back());
pvd::Type type = back->getField()->getType();
if(type==pvd::structure) {
// will fill in
} else if(type==pvd::structureArray) {
// starting new element in structure array
pvd::PVStructureArrayPtr sarr(std::tr1::static_pointer_cast<pvd::PVStructureArray>(back));
pvd::PVStructurePtr elem(pvd::getPVDataCreate()->createPVStructure(sarr->getStructureArray()->getStructure()));
self->stack.push_back(elem);
} else {
throw std::runtime_error("Can't map (sub)structure");
}
assert(self->stack.back()->getField()->getType()==pvd::structure);
return 1;
}CATCH()
}
int jtree_map_key(void * ctx, const unsigned char * key,
unsigned int stringLen)
{
TRY {
std::string name((const char*)key, stringLen);
// start_map() ensures we have a structure at the top of the stack
pvd::PVStructure *fld = static_cast<pvd::PVStructure*>(self->stack.back().get());
try {
self->stack.push_back(fld->getSubFieldT(name));
}catch(std::runtime_error& e){
std::ostringstream strm;
strm<<"At "<<fld->getFullName()<<" : "<<e.what()<<"\n";
throw std::runtime_error(strm.str());
}
return 1;
}CATCH()
}
int jtree_end_map(void * ctx)
{
TRY {
assert(self->stack.back()->getField()->getType()==pvd::structure);
pvd::PVStructurePtr elem(std::tr1::static_pointer_cast<pvd::PVStructure>(self->stack.back()));
self->stack.pop_back();
if(!self->stack.empty() && self->stack.back()->getField()->getType()==pvd::structureArray) {
// append element to struct array
pvd::PVStructureArray *sarr = static_cast<pvd::PVStructureArray*>(self->stack.back().get());
pvd::PVStructureArray::const_svector cval;
sarr->swap(cval);
pvd::PVStructureArray::svector val(pvd::thaw(cval));
val.push_back(elem);
sarr->replace(pvd::freeze(val));
}
return 1;
}CATCH()
}
int jtree_start_array(void * ctx)
{
TRY {
pvd::PVFieldPtr& back(self->stack.back());
pvd::Type type = back->getField()->getType();
if(type!=pvd::structureArray && type!=pvd::scalarArray)
throw std::runtime_error("Can't assign array");
return 1;
}CATCH()
}
int jtree_end_array(void * ctx)
{
TRY {
self->stack.pop_back();
return 1;
}CATCH()
}
yajl_callbacks jtree_cbs = {
&jtree_null,
&jtree_boolean,
&jtree_integer,
&jtree_double,
NULL, // number
&jtree_string,
&jtree_start_map,
&jtree_map_key,
&jtree_end_map,
&jtree_start_array,
&jtree_end_array,
};
struct handler {
yajl_handle handle;
handler(yajl_handle handle) :handle(handle)
{
if(!handle)
throw std::runtime_error("Failed to allocate yajl handle");
}
~handler() {
yajl_free(handle);
}
operator yajl_handle() { return handle; }
};
} // namespace
namespace epics{namespace pvData{
epicsShareFunc
void parseJSON(std::istream& strm,
const PVField::shared_pointer& dest)
{
yajl_parser_config conf = {
.allowComments = 1,
.checkUTF8 = 1,
};
context ctxt(dest);
handler handle(yajl_alloc(&jtree_cbs, &conf, NULL, &ctxt));
if(!yajl_parse_helper(strm, handle, conf))
throw std::runtime_error(ctxt.msg);
if(!ctxt.stack.empty())
throw std::logic_error("field stack not empty");
}
}} // namespace epics::pvData
#endif // EPICS_VERSION_INT

160
src/json/print.cpp Normal file
View File

@@ -0,0 +1,160 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <vector>
#include <sstream>
#include <pv/pvdVersion.h>
#include <pv/pvData.h>
#include <pv/valueBuilder.h>
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
#include <yajl_parse.h>
#define epicsExportSharedSymbols
#include "pv/json.h"
namespace pvd = epics::pvData;
namespace {
struct args {
std::ostream& strm;
const pvd::JSONPrintOptions& opts;
unsigned indent;
args(std::ostream& strm,
const pvd::JSONPrintOptions& opts)
:strm(strm)
,opts(opts)
,indent(opts.indent)
{}
void doIntent() {
if(!opts.multiLine) return;
strm.put('\n');
unsigned i=indent;
while(i--) strm.put(' ');
}
};
void show_field(args& A, const pvd::PVField* fld);
void show_struct(args& A, const pvd::PVStructure* fld)
{
const pvd::StructureConstPtr& type = fld->getStructure();
const pvd::PVFieldPtrArray& children = fld->getPVFields();
const pvd::StringArray& names = type->getFieldNames();
A.strm.put('{');
A.indent++;
for(size_t i=0, N=names.size(); i<N; i++)
{
if(i!=0)
A.strm.put(',');
A.doIntent();
A.strm<<'\"'<<names[i]<<"\": ";
show_field(A, children[i].get());
}
A.indent--;
A.doIntent();
A.strm.put('}');
}
void show_field(args& A, const pvd::PVField* fld)
{
switch(fld->getField()->getType())
{
case pvd::scalar:
{
const pvd::PVScalar *scalar=static_cast<const pvd::PVScalar*>(fld);
if(scalar->getScalar()->getScalarType()==pvd::pvString) {
A.strm<<'\"'<<scalar->getAs<std::string>()<<'\"';
} else {
A.strm<<scalar->getAs<std::string>();
}
}
break;
case pvd::scalarArray:
{
const pvd::PVScalarArray *scalar=static_cast<const pvd::PVScalarArray*>(fld);
const bool isstring = scalar->getScalarArray()->getElementType()==pvd::pvString;
pvd::shared_vector<const void> arr;
scalar->getAs<void>(arr);
pvd::shared_vector<const std::string> sarr(pvd::shared_vector_convert<const std::string>(arr));
A.strm.put('[');
for(size_t i=0, N=sarr.size(); i<N; i++) {
if(i!=0)
A.strm.put(',');
if(isstring)
A.strm.put('\"');
A.strm<<sarr[i];
if(isstring)
A.strm.put('\"');
}
A.strm.put(']');
}
break;
case pvd::structure:
show_struct(A, static_cast<const pvd::PVStructure*>(fld));
break;
case pvd::structureArray:
{
pvd::PVStructureArray::const_svector arr(static_cast<const pvd::PVStructureArray*>(fld)->view());
A.strm.put('[');
A.indent++;
for(size_t i=0, N=arr.size(); i<N; i++) {
if(i!=0)
A.strm.put(',');
A.doIntent();
if(arr[i])
show_struct(A, arr[i].get());
else
A.strm<<"NULL";
}
A.indent--;
A.doIntent();
A.strm.put(']');
}
break;
default:
if(A.opts.ignoreUnprintable)
A.strm<<"// unprintable field type";
else
throw std::runtime_error("Encountered unprintable field type");
}
}
} // namespace
namespace epics{namespace pvData{
JSONPrintOptions::JSONPrintOptions()
:multiLine(true)
,ignoreUnprintable(true)
,indent(0)
{}
void printJSON(std::ostream& strm,
const PVField::const_shared_pointer& val,
const JSONPrintOptions& opts)
{
args A(strm, opts);
show_field(A, val.get());
}
}} // namespace epics::pvData
#endif // EPICS_VERSION_INT

107
src/json/pv/json.h Normal file
View File

@@ -0,0 +1,107 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#ifndef PV_JSON_H
#define PV_JSON_H
#include <istream>
#include <ostream>
#include <string>
#include <map>
#include <pv/pvdVersion.h>
#include <pv/pvData.h>
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
#ifdef epicsExportSharedSymbols
# define pvjson_epicsExportSharedSymbols
# undef epicsExportSharedSymbols
#endif
#include <yajl_parse.h>
#ifdef pvjson_epicsExportSharedSymbols
# define epicsExportSharedSymbols
# include "shareLib.h"
#endif
#include <shareLib.h>
namespace epics{namespace pvData{
/** @defgroup pvjson JSON print/parse
*
* Printing PVField as JSON and parsing JSON into PVField.
*
* @{
*/
//! Options used during printing
struct epicsShareClass JSONPrintOptions
{
bool multiLine; //!< include new lines
bool ignoreUnprintable;//!< ignore union/union array when encountered
unsigned indent; //!< Initial indentation (# of spaces)
JSONPrintOptions();
};
/** Print PVStructure as JSON
*
* Restrictions:
*
* - No support for union or array of union
*/
epicsShareFunc
void printJSON(std::ostream& strm,
const PVField::const_shared_pointer& val,
const JSONPrintOptions& opts = JSONPrintOptions());
/** Parse JSON text into a PVStructure
*
* Restrictions:
*
* - Top level must be {} dict/object
* - field values must be number, string, array, or dict/object
* - array values must be number or string
*/
epicsShareFunc
PVStructure::shared_pointer parseJSON(std::istream& strm);
/** Parse JSON and store into the provided PVStructure.
*
* Restrictions:
*
* - Union or array of union not permitted
*/
epicsShareFunc
void parseJSON(std::istream& strm,
const PVField::shared_pointer& dest);
/** Wrapper around yajl_parse()
*
* Parse entire input stream.
* Errors if extranious non-whitespace found after the point were parsing completes.
*
* @param src The stream from which input charactors are read
* @param handle A parser handle previously allocated with yajl_alloc(). Not free'd on success or failure.
* @param config The same configuration passed to yajl_alloc(). Used to decide if trailing comments are allowed
*
* @returns true if parsing completes successfully. false if parsing cancelled by callback. throws other errors
*/
epicsShareFunc
bool yajl_parse_helper(std::istream& src,
yajl_handle handle,
const yajl_parser_config& config);
/** @} */
}} // namespace epics::pvData
#else
# error JSON parser requires EPICS Base >= 3.15.0.1
#endif // EPICS_VERSION_INT
#endif // PV_JSON_H

View File

@@ -113,6 +113,8 @@ inline testPassx testEqualx(const char *nLHS, const char *nRHS, LHS l, RHS r)
*/
#define testTrue(B) ::detail::testPassx(!!(B))<<#B
#define testThrows(EXC, CODE) try{ CODE; testFail("unexpected success of " #CODE); }catch(EXC& e){testPass("catch expected exception: %s", e.what());}
/** Compare value of PVStructure field
*
@code

View File

@@ -15,6 +15,10 @@
# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P))
#endif
#ifndef EPICS_VERSION_INT
# define EPICS_VERSION_INT VERSION_INT(EPICS_VERSION, EPICS_REVISION, EPICS_MODIFICATION, EPICS_PATCH_LEVEL)
#endif
/* include generated headers with:
* EPICS_PVD_MAJOR_VERSION
* EPICS_PVD_MINOR_VERSION

View File

@@ -72,3 +72,7 @@ TESTS += testTypeCast
TESTPROD_HOST += testUnitTest
testUnitTest_SRCS += testUnitTest.cpp
TESTS += testUnitTest
TESTPROD_HOST += testjson
testjson_SRCS += testjson.cpp
TESTS += testjson

240
testApp/misc/testjson.cpp Normal file
View File

@@ -0,0 +1,240 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <testMain.h>
#include <pv/pvdVersion.h>
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
#include <pv/json.h>
#include <pv/valueBuilder.h>
#include <pv/pvUnitTest.h>
namespace pvd = epics::pvData;
namespace {
void testparseany()
{
testDiag("testparseany()");
std::istringstream strm("{\"hello\":42, \"one\":{\"two\":\"test\"}}");
pvd::PVStructurePtr val(pvd::parseJSON(strm));
std::cout<<val<<"\n";
testFieldEqual<pvd::PVLong>(val, "hello", 42);
testFieldEqual<pvd::PVString>(val, "one.two", "test");
}
void testparseanyarray()
{
testDiag("testparseanyarray()");
std::istringstream strm("{\"hello\":[42, 43]}");
pvd::PVStructurePtr val(pvd::parseJSON(strm));
std::cout<<val<<"\n";
pvd::PVLongArray::svector arr(2);
arr[0] = 42;
arr[1] = 43;
pvd::PVLongArray::const_svector sarr(pvd::freeze(arr));
testFieldEqual<pvd::PVLongArray>(val, "hello", sarr);
}
void testparseanyjunk()
{
testDiag("testparseanyjunk()");
// things we can't store
{
testThrows(std::runtime_error, std::istringstream strm("4"); std::cout<<pvd::parseJSON(strm); );
}
{
testThrows(std::runtime_error, std::istringstream strm("[]"); std::cout<<pvd::parseJSON(strm); );
}
// junk found after parsing completes
{
testThrows(std::runtime_error, std::istringstream strm("{} x"); std::cout<<pvd::parseJSON(strm) );
}
{
testThrows(std::runtime_error, std::istringstream strm("{} /* y */"); std::cout<<pvd::parseJSON(strm) );
}
{
testThrows(std::runtime_error, std::istringstream strm("{} /* y *"); std::cout<<pvd::parseJSON(strm) );
}
{
testThrows(std::runtime_error, std::istringstream strm("{}\n\n{}"); std::cout<<pvd::parseJSON(strm) );
}
}
const char bigtest[] =
"{\"scalar\":42,\n"
" \"ivec\":[1,2,3], /*comment*/ \n"
" \"svec\":[\"one\", \"two\"],\n"
" \"sub\":{\"x\":{\"y\":43}},\n"
" \"sarr\":[\n"
" /* another comment */\n"
" {\"a\":5, \"b\":6}\n"
" ,{\"a\":7, \"b\":8}\n"
" ,{\"a\":9, \"b\":10}\n"
" ]\n"
"}";
pvd::StructureConstPtr bigtype(pvd::getFieldCreate()->createFieldBuilder()
->add("scalar", pvd::pvInt)
->addArray("ivec", pvd::pvLong)
->addArray("svec", pvd::pvString)
->addNestedStructure("sub")
->addNestedStructure("x")
->add("y", pvd::pvInt)
->endNested()
->endNested()
->addNestedStructureArray("sarr")
->add("a", pvd::pvInt)
->add("b", pvd::pvInt)
->endNested()
->createStructure()
);
void testInto()
{
testDiag("testInto()");
pvd::PVStructurePtr val(pvd::getPVDataCreate()->createPVStructure(bigtype));
std::istringstream strm(bigtest);
std::cout<<val;
pvd::parseJSON(strm, val);
std::cout<<val;
testFieldEqual<pvd::PVInt>(val, "scalar", 42);
testFieldEqual<pvd::PVInt>(val, "sub.x.y", 43);
{
pvd::PVLongArray::svector expect(3);
expect[0] = 1;
expect[1] = 2;
expect[2] = 3;
testFieldEqual<pvd::PVLongArray>(val, "ivec", pvd::freeze(expect));
}
{
pvd::PVStringArray::svector expect(2);
expect[0] = "one";
expect[1] = "two";
testFieldEqual<pvd::PVStringArray>(val, "svec", pvd::freeze(expect));
}
{
pvd::PVStructureArrayPtr sarr(val->getSubFieldT<pvd::PVStructureArray>("sarr"));
pvd::PVStructureArray::const_svector elems(sarr->view());
testEqual(elems.size(), 3u);
if(elems.size()<3)
testAbort("Missing elements");
testFieldEqual<pvd::PVInt>(elems[0], "a", 5);
testFieldEqual<pvd::PVInt>(elems[0], "b", 6);
testFieldEqual<pvd::PVInt>(elems[1], "a", 7);
testFieldEqual<pvd::PVInt>(elems[1], "b", 8);
testFieldEqual<pvd::PVInt>(elems[2], "a", 9);
testFieldEqual<pvd::PVInt>(elems[2], "b", 10);
}
}
void testroundtrip()
{
testDiag("testroundtrip()");
testDiag("Parse expected");
pvd::PVStructurePtr val(pvd::getPVDataCreate()->createPVStructure(bigtype));
{
std::istringstream strm(bigtest);
pvd::parseJSON(strm, val);
}
std::cout<<val;
testDiag("re-print parsed value");
std::string round1;
{
pvd::JSONPrintOptions opts;
opts.ignoreUnprintable = true;
opts.multiLine = true;
std::ostringstream strm;
pvd::printJSON(strm, val, opts);
round1 = strm.str();
}
std::cout<<round1<<"\n";
testDiag("re-parse re-printed value");
pvd::PVStructurePtr val2(pvd::getPVDataCreate()->createPVStructure(bigtype));
{
std::istringstream strm(round1);
pvd::parseJSON(strm, val2);
}
testEqual(*val, *val2);
testDiag("print value");
std::string round2;
{
pvd::JSONPrintOptions opts;
opts.ignoreUnprintable = true;
opts.multiLine = false;
std::ostringstream strm;
pvd::printJSON(strm, val, opts);
round2 = strm.str();
}
testEqual(round2, "{\"scalar\": 42,"
"\"ivec\": [1,2,3],"
"\"svec\": [\"one\",\"two\"],"
"\"sub\": {"
"\"x\": {"
"\"y\": 43"
"}},"
"\"sarr\": [{\"a\": 5,\"b\": 6},"
"{\"a\": 7,\"b\": 8},"
"{\"a\": 9,\"b\": 10}]"
"}");
}
} // namespace
MAIN(testjson)
{
testPlan(22);
try {
testparseany();
testparseanyarray();
testparseanyjunk();
testInto();
testroundtrip();
}catch(std::exception& e){
testAbort("Unexpected exception: %s", e.what());
}
return testDone();
}
#else // EPICS_VERSION_INT
MAIN(testjson)
{
testPlan(1);
try {
testSkip(1, "JSON parser requires Base >=3.15.0.1");
}catch(std::exception& e){
testAbort("Unexpected exception: %s", e.what());
}
return testDone();
}
#endif //EPICS_VERSION_INT