diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b1f621f7a1baaab963d5d36b6a06d327c7cdde1..4324612e0bc3cc61cc98d19ad596bdadf9baf788 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 2.8.12)
-
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/")
 set(IRODS_VERSION "4.1.x")
 message(STATUS "Building for irods-server version ${IRODS_VERSION}")
 
@@ -35,6 +35,12 @@ include_directories(${CURL_INCLUDE_DIRS})
 find_package(LibXml2 REQUIRED)
 include_directories(${LIBXML2_INCLUDE_DIR})
 
+find_package(LibXslt REQUIRED)
+include_directories(${LIBXSLT_INCLUDE_DIR})
+
+find_package(Jansson REQUIRED)
+include_directories(${JANSSON_INCLUDE_DIRS})
+
 include_directories("/usr/include/irods")
 
 add_library(msiSetUpperCaseWhereQuery SHARED src/msiSetUpperCaseWhereQuery.cc)
@@ -43,12 +49,19 @@ add_library(msiRegisterDataCiteDOI    SHARED src/msiRegisterDataCiteDOI.cc)
 add_library(msiGenerateRandomID       SHARED src/msiGenerateRandomID.cc)
 add_library(msiGetDataCiteDOI         SHARED src/msiGetDataCiteDOI.cc)
 add_library(msiLoadMetadataFromXml    SHARED src/msiLoadMetadataFromXml.cc)
-
-target_link_libraries(msiStrToUpper          LINK_PUBLIC ${Boost_LIBRARIES})
-target_link_libraries(msiRegisterDataCiteDOI LINK_PUBLIC ${CURL_LIBRARIES})
-target_link_libraries(msiGetDataCiteDOI      LINK_PUBLIC ${CURL_LIBRARIES})
-target_link_libraries(msiLoadMetadataFromXml LINK_PUBLIC ${LIBXML2_LIBRARIES})
-
+add_library(msiXmlDocSchemaValidate   SHARED src/msiXmlDocSchemaValidate.cc)
+add_library(msiXsltApply              SHARED src/msiXsltApply.cc)
+add_library(msi_json_arrayops         SHARED src/msi_json_arrayops.cc)
+add_library(msi_json_objops           SHARED src/msi_json_objops.cc)
+
+target_link_libraries(msiStrToUpper           LINK_PUBLIC ${Boost_LIBRARIES})
+target_link_libraries(msiRegisterDataCiteDOI  LINK_PUBLIC ${CURL_LIBRARIES})
+target_link_libraries(msiGetDataCiteDOI       LINK_PUBLIC ${CURL_LIBRARIES})
+target_link_libraries(msiLoadMetadataFromXml  LINK_PUBLIC ${LIBXML2_LIBRARIES})
+target_link_libraries(msiXmlDocSchemaValidate LINK_PUBLIC ${LIBXML2_LIBRARIES})
+target_link_libraries(msiXsltApply            LINK_PUBLIC ${LIBXML2_LIBRARIES} ${LIBXSLT_LIBRARIES} ${LIBXSLT_EXSLT_LIBRARIES})
+target_link_libraries(msi_json_arrayops      LINK_PUBLIC ${JANSSON_LIBRARIES} ${Boost_LIBRARIES})
+target_link_libraries(msi_json_objops      LINK_PUBLIC ${JANSSON_LIBRARIES} ${Boost_LIBRARIES})
 
 install(TARGETS
         msiSetUpperCaseWhereQuery
@@ -57,6 +70,10 @@ install(TARGETS
         msiGenerateRandomID
 	msiGetDataCiteDOI
 	msiLoadMetadataFromXml
+	msiXmlDocSchemaValidate
+	msiXsltApply
+	msi_json_arrayops
+	msi_json_objops
         DESTINATION
         /var/lib/irods/plugins/microservices)
 
@@ -75,7 +92,7 @@ set(CPACK_GENERATOR "RPM")
 set(CPACK_PACKAGE_NAME "irods-uu-microservices")
 set(CPACK_PACKAGE_VENDOR "Utrecht University <fbyoda@uu.nl>")
 set(CPACK_PACKAGE_CONTACT "Utrecht University <fbyoda@uu.nl>")
-set(CPACK_PACKAGE_VERSION "4.1.10_0.1.1")
+set(CPACK_PACKAGE_VERSION "4.1.11_0.3.0")
 
 set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/package/description.txt")
 set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Miscellaneous microservices developed by Utrecht university.")
@@ -87,6 +104,7 @@ set(CPACK_RPM_PACKAGE_LICENSE "LGPLv3")
 set(CPACK_RPM_PACKAGE_REQUIRES "boost-locale >= 1.51")
 set(CPACK_RPM_PACKAGE_REQUIRES "libcurl >= 7.29.0")
 set(CPACK_RPM_PACKAGE_REQUIRES "libxml2 >= 2.9.1")
+set(CPACK_RPM_PACKAGE_REQUIRES "libxslt >= 1.1.28")
 set(CPACK_RPM_PACKAGE_URL "https://github.com/UtrechtUniversity/irods-uu-microservices")
 set(CPACK_RPM_PACKAGE_AUTOREQ 0)
 set(CPACK_RPM_PACKAGE_AUTOPROV 0)
diff --git a/CMakeModules/FindJansson.cmake b/CMakeModules/FindJansson.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..bc0fdac32283baedd69e165d82062af4b0d00d00
--- /dev/null
+++ b/CMakeModules/FindJansson.cmake
@@ -0,0 +1,57 @@
+# - Try to find Jansson
+# Once done this will define
+#
+#  JANSSON_FOUND - system has Jansson
+#  JANSSON_INCLUDE_DIRS - the Jansson include directory
+#  JANSSON_LIBRARIES - Link these to use Jansson
+#
+#  Copyright (c) 2011 Lee Hambley <lee.hambley@gmail.com>
+#
+#  Redistribution and use is allowed according to the terms of the New
+#  BSD license.
+#  For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+#
+
+if (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS)
+  # in cache already
+  set(JANSSON_FOUND TRUE)
+else (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS)
+  find_path(JANSSON_INCLUDE_DIR
+    NAMES
+      jansson.h
+    PATHS
+      /usr/include
+      /usr/local/include
+      /opt/local/include
+      /sw/include
+  )
+
+find_library(JANSSON_LIBRARY
+    NAMES
+      jansson
+    PATHS
+      /usr/lib
+      /usr/local/lib
+      /opt/local/lib
+      /sw/lib
+  )
+
+set(JANSSON_INCLUDE_DIRS
+  ${JANSSON_INCLUDE_DIR}
+  )
+
+if (JANSSON_LIBRARY)
+  set(JANSSON_LIBRARIES
+    ${JANSSON_LIBRARIES}
+    ${JANSSON_LIBRARY}
+    )
+endif (JANSSON_LIBRARY)
+
+  include(FindPackageHandleStandardArgs)
+  find_package_handle_standard_args(Jansson DEFAULT_MSG
+    JANSSON_LIBRARIES JANSSON_INCLUDE_DIRS)
+
+  # show the JANSSON_INCLUDE_DIRS and JANSSON_LIBRARIES variables only in the advanced view
+  mark_as_advanced(JANSSON_INCLUDE_DIRS JANSSON_LIBRARIES)
+
+endif (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS)
diff --git a/README.md b/README.md
index fce75c317faae71b3377ce33c5649b98ca1521b0..442a7c61f66e10c4ba873c5e627c5a11a5edb39c 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,35 @@
 # irods-uu-microservices
-Miscellaneous microservices developed by Utrecht university.
+Miscellaneous iRODS microservices developed or modified by Utrecht university.
+
+
+msi\_json\_arrayops and msi\_json\_objops microservices are derived from
+work from the Donders Institute. The license below applies
+
+### Copyright and license for msi_json_arrayops and msi_json_objops
+
+This microservice has kindly been provided by the Donders Institute for
+Brain, Cognition and Behaviour.
+
+Copyright (c) 2016, Radboud University, Nijmegen, The Netherlands
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/package/description.txt b/package/description.txt
index 821fdeae99288574d30d0def5a6e7341c71add4f..af0984ac9bde7131ad741e9c591dae0663e9e586 100644
--- a/package/description.txt
+++ b/package/description.txt
@@ -1 +1,2 @@
-Miscellaneous microservices developed by Utrecht university.
+Miscellaneous microservices developed or modified by Utrecht university.
+
diff --git a/src/msiXmlDocSchemaValidate.cc b/src/msiXmlDocSchemaValidate.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5a828fc5112211916b4d258122ed6b5459bc6a7b
--- /dev/null
+++ b/src/msiXmlDocSchemaValidate.cc
@@ -0,0 +1,370 @@
+/**
+ * @file	msiXmlDocSchemaValidate.cpp
+ *
+ */ 
+
+/*** Copyright (c), The Regents of the University of California            ***
+ *** For more information please refer to files in the COPYRIGHT directory ***/
+
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+#include <libxml/uri.h>
+#include "apiHeaderAll.hpp"
+#include "msParam.hpp"
+#include "reGlobalsExtern.hpp"
+#include "irods_ms_plugin.hpp"
+#include "irods_server_api_call.hpp"
+#include "microservice.hpp"
+#include "objMetaOpr.hpp"
+#include "miscUtil.h"
+
+
+
+
+extern "C" {
+
+
+/* custom handler to catch XML validation errors and print them to a buffer */
+static int 
+myErrorCallback(bytesBuf_t *errBuf, const char* errMsg, ...)
+{
+	va_list ap;
+	char tmpStr[MAX_NAME_LEN];
+	int written;
+
+	va_start(ap, errMsg);
+	written = vsnprintf(tmpStr, MAX_NAME_LEN, errMsg, ap);
+	va_end(ap);
+
+	appendToByteBuf(errBuf, tmpStr);
+	return (written);
+}
+
+
+/**
+ * \fn msiXmlDocSchemaValidate(msParam_t *xmlObj, msParam_t *xsdObj, msParam_t *status, ruleExecInfo_t *rei)
+ *
+ * \brief  This microservice validates an XML file against an XSD schema, both iRODS objects.
+ * 
+ * \module xml
+ * 
+ * \since pre-2.1
+ * 
+ * \author  Antoine de Torcy
+ * \date    2008/05/29
+ * 
+ * \usage See clients/icommands/test/rules3.0/
+ *
+ * \param[in] xmlObj - a msParam of type DataObjInp_MS_T or STR_MS_T which is irods path of the XML object.
+ * \param[in] xsdObj - a msParam of type DataObjInp_MS_T or STR_MS_T which is irods path of the XSD object.
+ * \param[out] status - a msParam of type INT_MS_T which is a validation result.
+ * \param[in,out] rei - The RuleExecInfo structure that is automatically
+ *    handled by the rule engine. The user does not include rei as a
+ *    parameter in the rule invocation.
+ *
+ * \DolVarDependence None
+ * \DolVarModified None
+ * \iCatAttrDependence None
+ * \iCatAttrModified None
+ * \sideeffect None
+ *
+ * \return integer
+ * \retval 0 on success
+ * \pre None
+ * \post None
+ * \sa None
+**/
+int 
+msiXmlDocSchemaValidate(msParam_t *xmlObj, msParam_t *xsdObj, msParam_t *status, ruleExecInfo_t *rei) 
+{
+	/* for parsing msParams and to open iRODS objects */
+	dataObjInp_t xmlObjInp, *myXmlObjInp;
+	dataObjInp_t xsdObjInp, *myXsdObjInp;
+	int xmlObjID, xsdObjID;
+
+	/* for getting size of objects to read from */
+	rodsObjStat_t *rodsObjStatOut = NULL;
+
+	/* for reading from iRODS objects */
+	openedDataObjInp_t openedDataObjInp;
+	bytesBuf_t *xmlBuf = NULL;
+	xmlChar* xmlXmlChar;
+	xmlChar* xsdXmlChar;
+
+	/* for xml parsing and validating */
+	xmlDocPtr doc, xsd_doc;
+	xmlSchemaParserCtxtPtr parser_ctxt;
+	xmlSchemaPtr schema;
+	xmlSchemaValidCtxtPtr valid_ctxt;
+	bytesBuf_t *errBuf;
+
+	/* misc. to avoid repeating rei->rsComm */
+	rsComm_t *rsComm;
+
+
+
+	/*************************************  USUAL INIT PROCEDURE **********************************/
+	
+	/* For testing mode when used with irule --test */
+	RE_TEST_MACRO ("    Calling msiXmlDocSchemaValidate")
+
+
+	/* Sanity checks */
+	if (rei == NULL || rei->rsComm == NULL)
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: input rei or rsComm is NULL.");
+		return (SYS_INTERNAL_NULL_INPUT_ERR);
+	}
+
+	rsComm = rei->rsComm;
+
+
+
+	/************************************ ADDITIONAL INIT SETTINGS *********************************/
+
+	/* XML constants */
+	xmlSubstituteEntitiesDefault(1);
+	xmlLoadExtDtdDefaultValue = 1;
+
+
+	/* allocate memory for output error buffer */
+	errBuf = (bytesBuf_t *)malloc(sizeof(bytesBuf_t));
+	errBuf->buf = strdup("");
+	errBuf->len = strlen((char*)errBuf->buf);
+
+	/* Default status is failure, overwrite if success */
+	fillBufLenInMsParam (status, -1, NULL);
+
+	
+	/********************************** RETRIEVE INPUT PARAMS **************************************/
+
+	/* Get path of XML document */
+	rei->status = parseMspForDataObjInp (xmlObj, &xmlObjInp, &myXmlObjInp, 0);
+	if (rei->status < 0)
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: input xmlObj error. status = %d", rei->status);
+		free(errBuf);
+		return (rei->status);
+	}
+
+
+	/* Get path of schema */
+	rei->status = parseMspForDataObjInp (xsdObj, &xsdObjInp, &myXsdObjInp, 0);
+	if (rei->status < 0)
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: input xsdObj error. status = %d", rei->status);
+		free(errBuf);
+		return (rei->status);
+	}
+
+
+	/******************************** OPEN AND READ FROM XML OBJECT ********************************/
+
+	/* Open XML file */
+	if ((xmlObjID = rsDataObjOpen(rsComm, &xmlObjInp)) < 0) 
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Cannot open XML data object. status = %d", xmlObjID);
+		free(errBuf);
+		return (xmlObjID);
+	}
+
+
+	/* Get size of XML file */
+	rei->status = rsObjStat (rsComm, &xmlObjInp, &rodsObjStatOut);
+	if (rei->status < 0 || !rodsObjStatOut)
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Cannot stat XML data object. status = %d", rei->status);
+		free(errBuf);
+		return (rei->status);
+	}
+
+
+	/* xmlBuf init */
+	/* memory for xmlBuf->buf is allocated in rsFileRead() */
+	xmlBuf = (bytesBuf_t *) malloc (sizeof (bytesBuf_t));
+	memset (xmlBuf, 0, sizeof (bytesBuf_t));
+
+
+	/* Read content of XML file */
+	memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t));
+	openedDataObjInp.l1descInx = xmlObjID;
+	openedDataObjInp.len = (int)rodsObjStatOut->objSize;
+
+	rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xmlBuf);
+
+	xmlXmlChar = xmlCharStrndup((char*)xmlBuf->buf, xmlBuf->len);
+
+	/* Close XML file */
+	rei->status = rsDataObjClose (rsComm, &openedDataObjInp);
+
+	/* cleanup */
+	freeRodsObjStat (rodsObjStatOut);
+
+	/* content of xmlBuf can be discarded */
+	clearBBuf(xmlBuf);
+
+
+	/*************************************** PARSE XML DOCUMENT **************************************/
+
+	/* Parse xmlBuf.buf into an xmlDocPtr */
+	doc = xmlParseDoc(xmlXmlChar);
+
+	if (doc == NULL)
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: XML document cannot be loaded or is not well-formed.");
+		free(errBuf);
+	        /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls
+  		xmlCleanupParser();
+		*/
+
+	    return (USER_INPUT_FORMAT_ERR);
+	}
+
+
+
+	/******************************** OPEN AND READ FROM XSD OBJECT ********************************/
+
+	/* Open schema file */
+	if ((xsdObjID = rsDataObjOpen(rsComm, &xsdObjInp)) < 0) 
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Cannot open XSD data object. status = %d", xsdObjID);
+		free(errBuf);
+		xmlFreeDoc(doc);
+	        /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls
+  		xmlCleanupParser();
+		*/
+		return (xsdObjID);
+	}
+
+
+	/* Get size of schema file */
+	rei->status = rsObjStat (rsComm, &xsdObjInp, &rodsObjStatOut);
+
+
+	/* Read entire schema file */
+	memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t));
+	openedDataObjInp.l1descInx = xsdObjID;
+	openedDataObjInp.len = (int)rodsObjStatOut->objSize;
+
+	rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xmlBuf);
+
+	xsdXmlChar = xmlCharStrndup((char*)xmlBuf->buf, xmlBuf->len);
+
+	/* Close schema file */
+	rei->status = rsDataObjClose (rsComm, &openedDataObjInp);
+
+	/* cleanup */
+	freeRodsObjStat (rodsObjStatOut);
+
+	/* xmlBuf is no longer needed */
+	freeBBuf(xmlBuf);
+
+	/*************************************** PARSE XSD DOCUMENT **************************************/
+
+	xsd_doc = xmlParseDoc(xsdXmlChar);
+
+	if (xsd_doc == NULL)
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: XML Schema cannot be loaded or is not well-formed.");
+		free(errBuf);
+		xmlFreeDoc(doc);
+	        /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls
+		xmlCleanupParser();
+		*/
+	    return (USER_INPUT_FORMAT_ERR);
+	}
+
+
+
+	/**************************************** VALIDATE DOCUMENT **************************************/
+
+	/* Create a parser context */
+	parser_ctxt = xmlSchemaNewDocParserCtxt(xsd_doc);
+	if (parser_ctxt == NULL)
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Unable to create a parser context for the schema.");
+		free(errBuf);
+		xmlFreeDoc(xsd_doc);
+		xmlFreeDoc(doc);
+	        /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls
+		xmlCleanupParser();
+		*/
+	    return (USER_INPUT_FORMAT_ERR);
+	}
+
+
+	/* Parse the XML schema */
+	schema = xmlSchemaParse(parser_ctxt);
+	if (schema == NULL) 
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Invalid schema.");
+		free(errBuf);
+		xmlSchemaFreeParserCtxt(parser_ctxt);
+		xmlFreeDoc(doc);
+		xmlFreeDoc(xsd_doc);
+
+	        /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls
+		xmlCleanupParser();
+		*/
+
+	    return (USER_INPUT_FORMAT_ERR);
+	}
+
+
+	/* Create a validation context */
+	valid_ctxt = xmlSchemaNewValidCtxt(schema);
+	if (valid_ctxt == NULL) 
+	{
+		rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Unable to create a validation context for the schema.");
+		free(errBuf);
+		xmlSchemaFree(schema);
+		xmlSchemaFreeParserCtxt(parser_ctxt);
+		xmlFreeDoc(xsd_doc);
+		xmlFreeDoc(doc);
+		/* Don't call when libxml2 is used by other microservice calls
+		xmlCleanupParser();
+		*/
+	    return (USER_INPUT_FORMAT_ERR);
+	}
+
+
+	/* Set myErrorCallback() as the default handler for error messages and warnings */
+	xmlSchemaSetValidErrors(valid_ctxt, (xmlSchemaValidityErrorFunc)myErrorCallback, (xmlSchemaValidityWarningFunc)myErrorCallback, errBuf);
+
+
+	/* Validate XML doc */
+	rei->status = xmlSchemaValidateDoc(valid_ctxt, doc);
+
+
+
+	/******************************************* WE'RE DONE ******************************************/
+
+	/* return both error code and messages through status */
+	resetMsParam (status);
+	fillBufLenInMsParam (status, rei->status, errBuf);
+
+
+	/* cleanup of all xml parsing stuff */
+	xmlSchemaFreeValidCtxt(valid_ctxt);
+	xmlSchemaFree(schema);
+	xmlSchemaFreeParserCtxt(parser_ctxt);
+	xmlFreeDoc(doc);
+	xmlFreeDoc(xsd_doc);
+	
+	/* Don't call when libxml2 is used by other microservice calls
+	xmlCleanupParser();
+	*/
+
+	return (rei->status);
+}
+
+
+irods::ms_table_entry * plugin_factory() {
+    irods::ms_table_entry* msvc = new irods::ms_table_entry(3);
+    msvc->add_operation("msiXmlDocSchemaValidate","msiXmlDocSchemaValidate");
+    return msvc;
+}
+
+} // extern "C"
+
diff --git a/src/msiXsltApply.cc b/src/msiXsltApply.cc
new file mode 100644
index 0000000000000000000000000000000000000000..dc68f361292480ecb180c9027762e8462104115a
--- /dev/null
+++ b/src/msiXsltApply.cc
@@ -0,0 +1,272 @@
+/**
+ * @file	xsltMS.c
+ *
+ */ 
+
+/*** Copyright (c), The Regents of the University of California            ***
+ *** For more information please refer to files in the COPYRIGHT directory ***/
+#include <libxslt/transform.h>
+#include <libxslt/xsltutils.h>
+#include <libxslt/xsltInternals.h>
+#include <libexslt/exslt.h>
+#include <libexslt/exsltconfig.h>
+#include <iostream>
+#include "apiHeaderAll.hpp"
+#include "msParam.hpp"
+#include "reGlobalsExtern.hpp"
+#include "irods_ms_plugin.hpp"
+#include "irods_server_api_call.hpp"
+#include "microservice.hpp"
+#include "objMetaOpr.hpp"
+#include "miscUtil.h"
+
+
+extern int xmlLoadExtDtdDefaultValue;
+
+extern "C" {
+
+	/**
+	 * \fn msiXsltApply (msParam_t *xsltObj, msParam_t *xmlObj, msParam_t *msParamOut, ruleExecInfo_t *rei)
+	 *
+	 * \brief   This function applies an XSL stylesheet to an XML file, both existing iRODS objects.
+	 *
+	 * \module XML 
+	 *
+	 * \since pre-2.1
+	 *
+	 * \author  Antoine de Torcy
+	 * \date    2008/05/29
+	 *
+	 * \usage See clients/icommands/test/rules3.0/
+	 *
+	 * \param[in] xsltObj - a msParam of type DataObjInp_MS_T or STR_MS_T
+	 * \param[in] xmlObj - a msParam of type DataObjInp_MS_T or STR_MS_T
+	 * \param[out] msParamOut - a msParam of operation status BUF_LEN_MS_T
+	 * \param[in,out] rei - The RuleExecInfo structure that is automatically
+	 *    handled by the rule engine. The user does not include rei as a
+	 *    parameter in the rule invocation.
+	 *
+	 * \DolVarDependence None
+	 * \DolVarModified None
+	 * \iCatAttrDependence None
+	 * \iCatAttrModified None
+	 * \sideeffect None
+	 *
+	 * \return integer
+	 * \retval 0 on success
+	 * \pre None
+	 * \post None
+	 * \sa None
+	**/
+	int
+	msiXsltApply(msParam_t *xsltObj, msParam_t *xmlObj, msParam_t *msParamOut, ruleExecInfo_t *rei)
+	{
+		/* for parsing msParams and to open iRODS objects */
+		dataObjInp_t xmlDataObjInp, *myXmlDataObjInp;
+		dataObjInp_t xsltDataObjInp, *myXsltDataObjInp;
+		int xmlObjID, xsltObjID;
+
+		/* for getting size of objects to read from */
+		rodsObjStat_t *rodsObjStatOut = NULL;
+
+		/* for reading from iRODS objects */
+		openedDataObjInp_t openedDataObjInp;
+		bytesBuf_t *xmlBuf = NULL, *xsltBuf = NULL;
+
+		/* misc. to avoid repeating rei->rsComm */
+		rsComm_t *rsComm;
+
+		/* for xml parsing */
+		char *outStr;
+		int outLen;
+		xsltStylesheetPtr style = NULL;
+		xmlDocPtr xslSheet, doc, res;
+
+		/* for output buffer */
+		bytesBuf_t *mybuf;
+
+
+		/*********************************  USUAL INIT PROCEDURE **********************************/
+		
+		/* For testing mode when used with irule --test */
+		RE_TEST_MACRO ("    Calling msiXsltApply")
+
+
+		/* Sanity checks */
+		if (rei == NULL || rei->rsComm == NULL)
+		{
+			rodsLog (LOG_ERROR, "msiXsltApply: input rei or rsComm is NULL.");
+			return (SYS_INTERNAL_NULL_INPUT_ERR);
+		}
+
+		rsComm = rei->rsComm;
+
+
+		
+		/********************************** RETRIEVE INPUT PARAMS **********************************/
+
+		/* Get xsltObj: the XSLT stylesheet */
+		rei->status = parseMspForDataObjInp (xsltObj, &xsltDataObjInp, &myXsltDataObjInp, 0);
+		if (rei->status < 0)
+		{
+			rodsLog (LOG_ERROR, "msiXsltApply: input xsltObj error. status = %d", rei->status);
+			return (rei->status);
+		}
+
+
+		/* Get xmlObj: the XML document */
+		rei->status = parseMspForDataObjInp (xmlObj, &xmlDataObjInp, &myXmlDataObjInp, 0);
+		if (rei->status < 0)
+		{
+			rodsLog (LOG_ERROR, "msiXsltApply: input xmlObj error. status = %d", rei->status);
+			return (rei->status);
+		}
+
+
+
+		/******************************** GET CONTENTS OF IRODS OBJS ********************************/
+
+		/* Open XSLT file */
+		if ((xsltObjID = rsDataObjOpen(rsComm, &xsltDataObjInp)) < 0) 
+		{
+			rodsLog (LOG_ERROR, "msiXsltApply: Cannot open XSLT data object. status = %d", xsltObjID);
+			return (xsltObjID);
+		}
+
+
+		/* Get size of XSLT file */
+		rei->status = rsObjStat (rsComm, &xsltDataObjInp, &rodsObjStatOut);
+		if( NULL == rodsObjStatOut ) { // JMC cppcheck  nullptr ref
+			rodsLog( LOG_ERROR, "msiXsltApply:: null &rodsObjStatOut" );
+			return ( rei->status );
+		}
+
+		/* xsltBuf init */
+		/* memory for xsltBuf->buf is allocated in rsFileRead() */
+		xsltBuf = (bytesBuf_t *) malloc (sizeof (bytesBuf_t));
+		memset (xsltBuf, 0, sizeof (bytesBuf_t));
+
+
+		/* Read XSLT file */
+		memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t));
+		openedDataObjInp.l1descInx = xsltObjID;
+		openedDataObjInp.len = (int)rodsObjStatOut->objSize;
+
+		rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xsltBuf);
+		
+		/* Convert buffer to xmlChar (null-terminated) */
+
+		xmlChar* xsltXmlChar = xmlCharStrndup((char*)xsltBuf->buf, openedDataObjInp.len);		
+
+		/* Close XSLT file */
+		rei->status = rsDataObjClose (rsComm, &openedDataObjInp);
+
+
+		/* Cleanup. Needed before using rodsObjStatOut for a new rsObjStat() call */
+		freeRodsObjStat (rodsObjStatOut);
+
+		/* xsltBuf is no longer needed */
+		freeBBuf(xsltBuf);
+
+
+		/* Open XML file */
+		if ((xmlObjID = rsDataObjOpen(rsComm, &xmlDataObjInp)) < 0) 
+		{
+			rodsLog (LOG_ERROR, "msiXsltApply: Cannot open XML data object. status = %d", xmlObjID);
+			return (xmlObjID);
+		}
+
+
+		/* Get size of XML file */
+		rei->status = rsObjStat (rsComm, &xmlDataObjInp, &rodsObjStatOut);
+
+
+		/* xmlBuf init */
+		/* memory for xmlBuf->buf is allocated in rsFileRead() */
+		xmlBuf = (bytesBuf_t *) malloc (sizeof (bytesBuf_t));
+		memset (xmlBuf, 0, sizeof (bytesBuf_t));
+
+
+		/* Read XML file */
+		memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t));
+		openedDataObjInp.l1descInx = xmlObjID;
+		openedDataObjInp.len = (int)rodsObjStatOut->objSize;
+
+		rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xmlBuf);
+
+		/* copy buffer into null terminated xmlChar */
+		xmlChar* xmlXmlChar = xmlCharStrndup((char*)xmlBuf->buf, openedDataObjInp.len);		
+
+		/* Close XML file */
+		rei->status = rsDataObjClose (rsComm, &openedDataObjInp);
+
+		/* cleanup */
+		freeRodsObjStat (rodsObjStatOut);
+		/* xmlBuf is no longer needed */
+		freeBBuf(xmlBuf);
+
+
+		/******************************** PARSE XML DOCS AND APPLY XSL ********************************/
+
+		xmlSubstituteEntitiesDefault(1);
+		xmlLoadExtDtdDefaultValue = 1;
+		
+		/* register all available exslt extensions. This includes str:encode-uri */	
+		exsltRegisterAll();
+
+		/* Parse xsltXmlChar into an xmlDocPtr, and the xmlDocPtr into an xsltStylesheetPtr */
+		xslSheet = xmlParseDoc(xsltXmlChar);
+		style = xsltParseStylesheetDoc(xslSheet);
+
+		doc = xmlParseDoc(xmlXmlChar);
+
+		/* And the magic happens */
+		res = xsltApplyStylesheet(style, doc, NULL);
+
+		if (res == NULL) {
+			rei->status = XML_PARSING_ERR;
+		} else {
+			/* Save result XML document to a string */
+			rei->status = xsltSaveResultToString((xmlChar**)&outStr, &outLen, res, style);
+		}
+
+
+		/* cleanup of all xml parsing stuff */
+		xsltFreeStylesheet(style);
+		xmlFreeDoc(res);
+		xmlFreeDoc(doc);
+
+		/* don't call if libxslt or libxml2 are called in other microservices
+		xsltCleanupGlobals();
+		xmlCleanupParser();
+		*/
+
+		/************************************** WE'RE DONE **************************************/
+
+		if (rei->status != XML_PARSING_ERR) {
+			/* copy the result string into an output buffer */
+			mybuf = (bytesBuf_t *)malloc(sizeof(bytesBuf_t));
+			mybuf->buf = (void *)outStr;
+			mybuf->len = strlen(outStr);
+
+
+			/* send results out to msParamOut */
+			fillBufLenInMsParam (msParamOut, mybuf->len, mybuf);
+		}
+
+		return (rei->status);
+	}
+
+
+	irods::ms_table_entry* plugin_factory() {
+		irods::ms_table_entry* msvc = new irods::ms_table_entry(3);
+
+		msvc->add_operation( "msiXsltApply", "msiXsltApply" );
+
+		return msvc;
+	}
+
+
+
+}; // extern "C"
+
diff --git a/src/msi_json_arrayops.cc b/src/msi_json_arrayops.cc
new file mode 100644
index 0000000000000000000000000000000000000000..91021da5520aa2685a5b2b2e213ccc50ba22b33b
--- /dev/null
+++ b/src/msi_json_arrayops.cc
@@ -0,0 +1,131 @@
+// =-=-=-=-=-=-=-
+#include "apiHeaderAll.hpp"
+#include "msParam.hpp"
+#include "reGlobalsExtern.hpp"
+#include "irods_ms_plugin.hpp"
+#include "reFuncDefs.hpp"
+#include "jansson.h"
+
+// =-=-=-=-=-=-=-
+// STL/boost Includes
+#include <string>
+#include <iostream>
+#include <vector>
+//#include <boost/algorithm/string.hpp>
+
+extern "C" {
+    // =-=-=-=-=-=-=-
+    int msi_json_arrayops_impl(msParam_t* json_str, msParam_t* val, msParam_t* ops, msParam_t* sizeOrIndex, ruleExecInfo_t* rei) {
+        using std::cout;
+        using std::endl;
+        using std::string;
+
+        // input type check
+        const char *inJsonStr = parseMspForStr( json_str );
+        const char *inVal = parseMspForStr( val );
+        const char *inOps = parseMspForStr( ops );
+        const int inIdx = parseMspForPosInt( sizeOrIndex );
+
+        if( ! inJsonStr ) {
+            cout << "msi_json_arrayops - invalid inJsonStr" << endl;
+            return SYS_INVALID_INPUT_PARAM;
+        }
+        if( ! inOps ) {
+            cout << "msi_json_arrayops - invalid inOps" << endl;
+            return SYS_INVALID_INPUT_PARAM;
+        }
+        if( ! inVal ) {
+            cout << "msi_json_arrayops - invalid inVal" << endl;
+            return SYS_INVALID_INPUT_PARAM;
+        }
+
+        string strOps(inOps); 
+
+        json_error_t error;
+        json_t *root;
+
+        // try to make initial inJsonStr if it's an empty string 
+        if ( strcmp(inJsonStr, "") == 0 ) {
+            inJsonStr = "[]";
+        }
+
+        // try to load JSON object
+        root = json_loads(inJsonStr, 0, &error);
+        if ( ! root || ! json_is_array(root) ) {
+            cout << "msi_json_arrayops - invalid json document" << endl;
+            json_decref(root);
+            return SYS_INVALID_INPUT_PARAM;
+        }
+
+        int outSizeOrIndex = (int) json_array_size(root);
+        json_t *jval;
+        if (strcmp(inVal, "null") == 0) {
+            jval = json_null();
+        } else if (strcmp(inVal, "true") == 0 ) {
+            jval = json_true();
+        } else if (strcmp(inVal, "false") == 0 ) {
+            jval = json_false();
+        } else {
+            jval = json_loads(inVal, 0, &error);
+            if ( ! jval ) jval = json_string(inVal);
+        }
+
+        // find if jval is already presented in array
+        size_t i_match = outSizeOrIndex;
+        for( int i=0; i < outSizeOrIndex; i++ ) {
+            json_t *ov = json_array_get(root, i);
+            if ( json_equal(ov, jval) ) { i_match = i; break; }
+        }
+
+        if ( strOps == "add" ) {
+            // append value only if it's a boolean, or it's not presented in the array
+            if ( json_is_boolean(jval) || i_match == outSizeOrIndex ) {
+                json_array_append_new(root, jval);
+                outSizeOrIndex = (int) json_array_size(root);
+            }
+        } else if ( strOps == "find" || strOps == "rm" ) {
+
+            if ( i_match < outSizeOrIndex ) {
+                if ( strOps == "rm" ) {
+                    json_array_remove(root, i_match);
+                    outSizeOrIndex = (int) json_array_size(root);
+                } else {
+                    outSizeOrIndex = i_match;
+                }
+            } else if ( strOps == "find" ) {
+                outSizeOrIndex = -1;
+            }
+        } else if ( strOps == "size" ) {
+            outSizeOrIndex = (int) json_array_size(root);
+        } else if ( strOps == "get" ) {
+            json_t *elem = json_array_get(root, inIdx);
+
+	    /* output a string directly, but encode other json types using json_dumps with JSON_ENCODE_ANY set */
+            if ( json_is_string(elem)) { 
+                fillStrInMsParam(val, json_string_value(elem));
+            } else {
+                fillStrInMsParam(val, json_dumps(elem, JSON_ENCODE_ANY));
+            }
+
+
+            outSizeOrIndex = inIdx;
+        }
+
+        fillStrInMsParam(json_str, json_dumps(root, 0));
+        fillIntInMsParam(sizeOrIndex, outSizeOrIndex);
+
+        json_decref(root);
+
+        // Done
+        return 0;
+    }
+
+    irods::ms_table_entry* plugin_factory() {
+        irods::ms_table_entry* msvc = new irods::ms_table_entry(4);
+        
+        msvc->add_operation("msi_json_arrayops_impl", "msi_json_arrayops");
+        
+        return msvc;
+    }
+
+} // extern "C"
diff --git a/src/msi_json_objops.cc b/src/msi_json_objops.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1ec7540bc4e05ca66882bd5f5cce1de96fc9a9ab
--- /dev/null
+++ b/src/msi_json_objops.cc
@@ -0,0 +1,140 @@
+// =-=-=-=-=-=-=-
+#include "apiHeaderAll.hpp"
+#include "msParam.hpp"
+#include "reGlobalsExtern.hpp"
+#include "irods_ms_plugin.hpp"
+#include "reFuncDefs.hpp"
+#include "jansson.h"
+
+// =-=-=-=-=-=-=-
+// STL/boost Includes
+#include <string>
+#include <iostream>
+#include <vector>
+#include <boost/algorithm/string.hpp>
+
+extern "C" {
+    // =-=-=-=-=-=-=-
+    int msi_json_objops_impl(msParam_t* json_str, msParam_t* kvp, msParam_t* ops, ruleExecInfo_t* rei) {
+        using std::cout;
+        using std::endl;
+        using std::string;
+
+        char *null_cstr = new char[5];
+        char *true_cstr = new char[5];
+        char *false_cstr = new char[6];
+
+        strcpy(null_cstr, "null");
+        strcpy(true_cstr, "true");
+        strcpy(false_cstr, "false");
+
+        // input type check
+        const char *inJsonStr = parseMspForStr( json_str );
+        const char *inOps = parseMspForStr( ops );
+        if( ! inJsonStr || ! inOps) {
+            cout << "msi_json_objops - invalid input for string" << endl;
+            return SYS_INVALID_INPUT_PARAM;
+        }
+
+        if (kvp == NULL || kvp->inOutStruct == NULL ||
+            kvp->type == NULL || strcmp(kvp->type, KeyValPair_MS_T) != 0) {
+            cout << "msi_json_objops - invalid input for key-value pairs" << endl;
+            return SYS_INVALID_INPUT_PARAM;
+        }
+
+        keyValPair_t *inKVP;
+        inKVP = (keyValPair_t*) kvp->inOutStruct;
+
+        string strOps(inOps); 
+
+        json_error_t error;
+        json_t *root;
+
+        // try to make initial inJsonStr if it's an empty string 
+        if ( strcmp(inJsonStr, "") == 0 ) {
+            inJsonStr = "{}";
+        }
+
+        // try to load JSON object
+        root = json_loads(inJsonStr, 0, &error);
+        if ( ! root ) {
+            cout << "msi_json_objops - invalid json document" << endl;
+            json_decref(root);
+            return SYS_INVALID_INPUT_PARAM;
+        }
+
+        for (int ik=0; ik<inKVP->len; ik++) {
+
+            char* inKey = inKVP->keyWord[ik];
+            char* inVal = inKVP->value[ik];
+
+            json_t *jval;
+            if (strcmp(inVal, null_cstr) == 0) {
+                jval = json_null();
+            } else {
+                jval = json_loads(inVal, 0, &error);
+                if ( ! jval ) jval = json_string(inVal);
+            }
+ 
+            // try to find objects in the key
+            json_t *data = json_object_get(root, inKey);
+
+            if ( strOps == "get" ) {
+               if ( json_is_null(data) ) {
+                   inKVP->value[ik] = null_cstr;
+               } else if ( json_is_true(data) ) {
+                   inKVP->value[ik] = true_cstr;
+               } else if ( json_is_false(data) ) {
+                   inKVP->value[ik] = false_cstr;
+               } else if ( json_is_string(data) ) { 
+                   const char* val_str = json_string_value(data);
+                   char *val_cstr = new char[strlen(val_str) + 1];
+                   strcpy(val_cstr, val_str);
+                   inKVP->value[ik] = val_cstr;
+               } else {
+                   inKVP->value[ik] = json_dumps(data,0);
+               }
+            } else if ( strOps == "add" ) {
+                if (json_is_array( data )) {
+                    json_array_append_new(data, jval);
+                } else {
+                    json_object_set_new(root, inKey, jval);
+                }
+            } else if ( strOps == "set" ) {
+                json_object_set_new(root, inKey, jval);
+            } else if ( strOps == "rm" )  {
+                if ( data ) {
+                    if ( json_is_array( data ) ) {
+                        size_t i_match = json_array_size(data);
+                        for( int i=0; i<json_array_size(data); i++ ) {
+                            json_t *ov = json_array_get(data, i);
+                            if ( json_equal(ov, jval) ) { i_match = i; break; }
+                        }
+                        if ( i_match < json_array_size(data) ) json_array_remove(data, i_match);
+                    } else if ( json_equal(data, jval) ) {
+                        json_object_del(root, inKey);
+                    }
+                }
+            }
+        }
+
+        kvp->inOutStruct = (void *) inKVP;
+        kvp->type = (char *) strdup(KeyValPair_MS_T);
+
+        fillStrInMsParam(json_str, json_dumps(root, 0));
+
+        json_decref(root);
+
+        // Done
+        return 0;
+    }
+
+    irods::ms_table_entry* plugin_factory() {
+        irods::ms_table_entry* msvc = new irods::ms_table_entry(3);
+        
+        msvc->add_operation("msi_json_objops_impl", "msi_json_objops");
+        
+        return msvc;
+    }
+
+} // extern "C"
diff --git a/tests/msi_json_arrayops_test.r b/tests/msi_json_arrayops_test.r
new file mode 100644
index 0000000000000000000000000000000000000000..5e41b1a188215f0301bd2a123bdfe72f7dcecbf9
--- /dev/null
+++ b/tests/msi_json_arrayops_test.r
@@ -0,0 +1,66 @@
+myTestRule {
+
+   *json_str = '[]';
+
+   *item1 = 'test & and %';
+   *item2 = 'U123456-ru.nl';
+   *item3 = '{"type":"PMID","id":"12345"}';
+   *item4 = '{"type":"arXiv","id":"xyz/abc"}';
+   *item5 = 'null';
+   *item6 = 'true';
+   *item7 = 'false';
+   *item8 = '["DICOM","test"]';
+
+   ## build JSON array
+   *size = 0;
+   *ec = errorcode( msi_json_arrayops(*json_str, *item1, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item2, "add", *size) );
+   *ec = errorcode( msi_json_arrayops(*json_str, *item3, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item4, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item6, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item7, "add", *size) );
+   *ec = errorcode( msi_json_arrayops(*json_str, *item8, "add", *size) );
+
+    # adding null does not supported? 
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item5, "add", *size) );
+
+    # adding those will append additional boolean values to array 
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item6, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item7, "add", *size) );
+
+    # adding those will not change array because they have been presented in it
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item1, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item2, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item3, "add", *size) );
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item4, "add", *size) );
+
+   writeLine("stdout", *json_str ++ " size: " ++ *size);
+
+   ## remove JSON object (rm/find)
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item3, "rm", *size) );
+   #writeLine("stdout", *json_str ++ " size: " ++ *size);
+
+   #*idx = 0;
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item5, "find", *idx) );
+   #writeLine("stdout", *json_str ++ " " ++ *item5 ++ " at idx: " ++ str(*idx));
+
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item4, "find", *idx) );
+   #writeLine("stdout", *json_str ++ " " ++ *item4 ++ " at idx: " ++ str(*idx));
+
+   #*ec = errorcode( msi_json_arrayops(*json_str, *item1, "find", *idx) );
+   #writeLine("stdout", *json_str ++ " " ++ *item1 ++ " at idx: " ++ str(*idx));
+
+   #*ec = errorcode( msi_json_arrayops(*json_str, "", "size", *idx) );
+   #writeLine("stdout", *json_str ++ " size: " ++ str(*idx));
+
+   *mysize = 0;
+   *ec = errorcode( msi_json_arrayops(*json_str, "", "size", *mysize) );
+   writeLine("stdout", *json_str ++ " size: " ++ str(*mysize));
+
+   *idx = 0;
+   *item1 = "";
+   *ec = errorcode( msi_json_arrayops(*json_str, *item1, "get", *idx) );
+   writeLine("stdout", *json_str ++ " " ++ *item1 ++ " at idx: " ++ str(*idx));
+
+}
+OUTPUT ruleExecOut
diff --git a/tests/msi_json_objops_test.r b/tests/msi_json_objops_test.r
new file mode 100644
index 0000000000000000000000000000000000000000..10697a586dc17075d2fc1652a51af5f40a207670
--- /dev/null
+++ b/tests/msi_json_objops_test.r
@@ -0,0 +1,42 @@
+myTestRule {
+
+   *json_str = "";
+
+   ## build JSON object from key-value pairs
+   msiString2KeyValPair("", *kvp);
+   msiAddKeyVal(*kvp, 'type', 'DATA_SHARING');
+   msiAddKeyVal(*kvp, 'collId', '12345');
+   msiAddKeyVal(*kvp, 'title', 'DICOM 原生資料備份');
+   msiAddKeyVal(*kvp, 'creatorList', '["U505173-ru.nl","hurngchunlee-icloud.com"]');
+   msiAddKeyVal(*kvp, 'associatedPublication', '[{"type":"PMID", "id":"654321"}]');
+
+   *ec = errorcode( msi_json_objops(*json_str, *kvp, "add") );
+   writeLine("stdout", *json_str);
+
+   ## modify JSON object (add/set/get/rm)
+   msiString2KeyValPair("", *kvp);
+   msiAddKeyVal(*kvp, 'associatedPublication', '{"type":"arXiv", "id":"xyz/123"}');
+   *ec = errorcode( msi_json_objops(*json_str, *kvp, "add") );
+   writeLine("stdout", *json_str);
+  
+   msiString2KeyValPair("", *kvp);
+   msiAddKeyVal(*kvp, 'associatedPublication', '{"type":"PMID", "id":"654321"}');
+   *ec = errorcode( msi_json_objops(*json_str, *kvp, "rm") );
+   writeLine("stdout", *json_str);
+
+   msiString2KeyValPair("", *kvp);
+   msiAddKeyVal(*kvp, 'creatorList', '["hurngchunlee-icloud.com","U505173-ru.nl"]');
+   *ec = errorcode( msi_json_objops(*json_str, *kvp, "set") );
+   writeLine("stdout", *json_str);
+
+   msiString2KeyValPair("", *kvp);
+   msiAddKeyVal(*kvp, 'creatorList', '');
+   msiAddKeyVal(*kvp, 'associatedPublication', '');
+   msiAddKeyVal(*kvp, 'collId', '');
+   msiAddKeyVal(*kvp, 'title', '');
+   #msiAddKeyVal(*kvp, 'associatedPublication', '');
+   *ec = errorcode( msi_json_objops(*json_str, *kvp, "get") );
+   writeLine("stdout", str(*kvp));
+}
+OUTPUT ruleExecOut
+