********************************************************************************
*                                                                              *
* Description -                                                                *
*   Program to serialize a dataset and return a JSON representation of the     *
*   data.                                                                      *
*                                                                              *
* System Requirements -                                                        *
*   JRE 1.8 or Higher.                                                         *
*                                                                              *
* Output - (Optional)                                            			   *
*   Either prints a JSON object to the Stata console or writes one to disk.    *
*                                                                              *
* Lines -                                                                      *
* 	322	                                                                       *
*                                                                              *
********************************************************************************

*! jsonio
*! 27APR2017
*! v 0.0.7

// Drop program from memory if it exists
cap prog drop jsonio

// Define program as an r-class program
prog def jsonio, rclass

	// Set version
	version 13.0

	// Define input syntax
	syntax anything(name=subtype id="Input/Output Type") [if] [in] , [		 ///
	ELEMents(passthru) noURL FILEnm(string) OBid(passthru) 					 ///
	METAprint(passthru)  What(passthru) STUBname(passthru) ]

	// Tokenize the first argument
	gettoken cmd opts : subtype

	// Make sure there aren't any issues with the macro expansion for filepaths
	if `"`url'"' == "nourl" loc filenm `: subinstr loc filenm `"~"' `"`: env HOME'"''

	// If command is keyvay
	if `"`cmd'"' == "kv" keyval `"`filenm'"', `elements' `url'

	// If the command is rowvalue orientation
	else if `"`cmd'"' == "rv" rowval `"`filenm'"', `elements' `url' `obid' `stubname'

	// For output need to pass the if/in conditions to the subroutine call
	else jsonout `opts' `if' `in', filenm(`filenm') `obid' `metaprint' `what'

	// Return local with the total number of keys
	ret loc totalkeys `r(totalkeys)'

	// Set the local macro that needs to be returned
	ret loc thejson `"`r(thejson)'"'

// End of the program definition
end

// JSON Deserializer
prog def rowval, rclass

	// Define input syntax
	syntax anything(name=source id="Source of the JSON Input") [,			 ///
	ELEMents(string) noURL OBid(integer 0) STUBname(string asis) ]

	// If elements is null
	if `"`elements'"' == "" loc elements ".*"

	// Use default stubname if none specified
	if "`stubname'" == "" loc stubname jsonvar

	// Assumes data is coming from a URL unless specified otherwise
	if `"`url'"' == "nourl" {

		// Call Java method to import from file
		javacall org.paces.Stata.Input.InJSON insheetFileToVars,			 ///
		args(`source' "`elements'" "`obid'" "`stubname'")

	} // End IF Block for files

	// If it is a URL
	else {

		// Call Java method to import from URL
		javacall org.paces.Stata.Input.InJSON insheetUrlToVars,				 ///
		args(`source' "`elements'" "`obid'")

	} // End ELSE Block for URLs

	// Return local with the total number of keys
	ret loc totalkeys `totalkeys'

// End subroutine
end

// JSON Deserializer
prog def keyval, rclass

	// Define input syntax
	syntax anything(name=source id="Source of the JSON Input") [,			 ///
	ELEMents(string) noURL ]

	// If elements is null
	if `"`elements'"' == "" loc elements ".*"

	// Assumes data is coming from a URL unless specified otherwise
	if `"`url'"' == "nourl" {

		// Call Java method to import from file
		javacall org.paces.Stata.Input.InJSON insheetFile,					 ///
		args(`source' "`elements'")

	} // End IF Block for files

	// If it is a URL
	else {

		// Call Java method to import from URL
		javacall org.paces.Stata.Input.InJSON insheetUrl, args(`source' "`elements'")

	} // End ELSE Block for URLs

	// Return local with the total number of keys
	ret loc totalkeys `totalkeys'

// End subroutine
end

// JSON Serializer
prog def jsonout, rclass

	// Define syntax
	syntax [varlist] [if] [in] [using/] , 				 					 ///
	[ FILEnm(string asis) OBid(real 0) METAprint(string asis)                ///
	What(string asis) COMMents YAML UQFNames SQuotes UQControl BACKSLash     ///
	LEADZero NONNUM DUPlicates UNDEFined UTF16 ]

    // Normalize *nix home shortcut in file
    if substr(`"`filenm'"', 1, 1) == "~" {
        loc filenm `: subinstr loc filenm `"~"' `"`: env HOME'"', all'
    }

    /* Used to enable the ALLOW_COMMENTS property of
    the JsonParser Java class from the Jackson API.  For additional information
    interested users should see:
        http://fasterxml.github.io/jackson-core/javadoc/2.6/
    */
	if `"`comments'"' == "" loc comments false
	else loc comments true

    /* Used to enable the ALLOW_YAML_COMMENTS property of the JsonParser Java
     class from the Jackson API. */
	if `"`yaml'"' == "" loc yaml false
	else loc yaml true

    /* Used to enable the ALLOW_UNQUOTED_FIELD_NAMES property of the JsonParser Java
     class from the Jackson API. */
	if `"`uqfnames'"' == "" loc uqfnames false
	else loc uqfnames true

    /* Used to enable the ALLOW_SINGLE_QUOTES property of the JsonParser Java
     class from the Jackson API. */
	if `"`squotes'"' == "" loc squotes false
	else loc squotes true

    /* Used to enable the ALLOW_UNQUOTED_CONTROL_CHARS property of the JsonParser Java
     class from the Jackson API. */
	if `"`uqcontrol'"' == "" loc uqcontrol false
	else loc uqcontrol true

    /* Used to enable the ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER property of the
    JsonParser Java class from the Jackson API. */
	if `"`backslash'"' == "" loc backslash false
	else loc backslash true

    /* Used to enable the ALLOW_NUMERIC_LEADING_ZEROS property of the JsonParser Java
     class from the Jackson API. */
	if `"`leadzero'"' == "" loc leadzero false
	else loc leadzero true

    /* Used to enable the ALLOW_NON_NUMERIC_NUMBERS property of the JsonParser Java
     class from the Jackson API. */
	if `"`nonnum'"' == "" loc nonnum false
	else loc nonnum true

    /* Used to enable the STRICT_DUPLICATE_DETECTION property of the JsonParser Java
     class from the Jackson API. */
	if `"`duplicates'"' == "" loc duplicates false
	else loc duplicates true

    /* Used to enable the IGNORE_UNDEFINED property of the JsonParser Java
     class from the Jackson API. */
	if `"`undefined'"' == "" loc undefined false
	else loc undefined true

	// Gets maximum length of a macro for the user
	loc maxlen `= `c(maxvar)' * 129'

    // Set local macro with the file name
    loc filename `"`c(filename)'"'

    // Clean up file path using tilde shortcut
    loc filenm `: subinstr loc filenm `"~"' `"`: env HOME'"''

	// Check for aguments defining what to convert
	if inlist(proper("`what'"), "Record", "Data", "All") & "`metaprint'" == "" {

		// Make value proper cased
		loc what = proper("`what'")

	} // End IF Block for what argument

	// If All arguments are missing
	else if "`what'" == "" & "`metaprint'" == "" {

		// Print message to Stata console
		di as res "No arguments provided to define what to serialize." _n ///
		"Will serialize entire dataset by default."

		// Set the what macro to Data
		loc what Data

	} // End ELSE IF Block for options to define what to serialize

	// Preserve the data
	preserve

		// Check for using option
		if `"`using'"' != "" {

			// Load the specified file
			qui: use `"`using'"', clear

		} // End IF Block to load data from using

		// Check for metadata argument
		if inlist(`"`metaprint'`filenm'"', "varlabels", "varnames", "vallabs", ///
		"labelnames") {

			// Call java method to print metadata
			javacall org.paces.Stata.JSON.StataMetaToJSON metaToJSON 		 ///
			`varlist' `if' `in', args(`utf16')

		} // End IF Block for printing metadata to JSON

		// For non-metadata based calls
		else {

			// For non metadata cases
			if "`what'" == "Data" & "`filenm'" != "" {

				// Call java method to write JSON object to disk
				javacall org.paces.Stata.JSON.StataJSON printDataToFile 	 ///
				`varlist' `if' `in'

			} // End if block for printing data to JSON

			// Call to print dataset to Stata console
			else if "`what'" == "Data" & "`filenm'" == "" {

				// Call java method to write JSON object to the Stata console
				javacall org.paces.Stata.JSON.StataJSON printData `varlist'  ///
				`if' `in'

			} // End ELSEIF Block to print dataset to Stata console

			// Call to write individual record to file
			else if "`what'" == "Record" & "`filenm'" != "" {

				// Call java method to write individual record to disk
				javacall org.paces.Stata.JSON.StataJSON printRecordToFile 	 ///
				`varlist' `if' `in'

			} // End ELSEIF Block for writing individual record to disk

			// Option to print individual record to the screen
			else if "`what'" == "Record" & "`filenm'" == "" {

				// Call java method to print record to the Stata console
				javacall org.paces.Stata.JSON.StataJSON printRecord 		 ///
				`varlist' `if' `in'

			} // End ELSEIF Block to print JSON record to Stata console

			// Option to print everything to the console
			else if "`what'" == "All" & "`filenm'" == "" {

				// Call java method to print everything to the Stata console
				javacall org.paces.Stata.JSON.StataJSON printAll `varlist'   ///
				`if' `in'

			} // End ELSEIF Block to print JSON data/metadata to Stata console

			// Option to write all data to file on the disk
			else {

				// Call java method to print everything to the Stata console
				javacall org.paces.Stata.JSON.StataJSON printAllToFile       ///
				`varlist' `if' `in'

			} // End ELSE Block to print JSON data/metadata to disk

		} // End ELSE Block for data-based function calls

		// Put default error message in the macro
		if `"`thejson'"' == "" ret loc thejson "JSON Object Too Large for Macro."

		// Set the local macro that needs to be returned
		else ret loc thejson `"`thejson'"'

	// Restore data to that originally in memory
	restore

	// Set all of the local macros to null
	loc filenm
	loc what
	loc obid
	loc metaprint

// End function definition
end