Custom script examples (general)

Introduction

This page includes scripts that may be useful either in and of themselves or as a starting point for your own scripting work:

C#

This script takes an XML input string, loads it into an XmlDocument, and then converts that XML to a JSON string using the Newtonsoft.Json library.

Converting XML to JSON
using System;
using System.Xml;
using Newtonsoft.Json;
public class FunctionHandler
{
    public string Handle(string input)
    {
        var xmlDoc = new XmlDocument();
        
        // Set up XML namespace manager
        var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
        namespaceManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
        namespaceManager.AddNamespace("", "http://api.jdplc.com/schemas/mc/pim/feproducts");
        
        // Load the XML string into XmlDocument
        xmlDoc.LoadXml(input);

        // Convert the XML to JSON
        string jsonText = JsonConvert.SerializeXmlNode(xmlDoc, Newtonsoft.Json.Formatting.Indented, true);

        return jsonText;
    }
}

Go

The script below demonstrates the handling of variables, logging, updating response codes, and modifying payloads.

Helper functions demo
package function

import (
	"encoding/json"
	"fmt"
)

type Variables map[string]string

func (v *Variables) UnmarshalJSON(bytes []byte) error {
	if string(bytes) == "[]" {
		*v = make(Variables)
	} else {
		d := make(map[string]string)
		err := json.Unmarshal(bytes, &d)
		if err != nil {
			return err
		}

		*v = d
	}

	return nil
}

type Request struct {
	Payload   string    `json:"payload"`
	Variables Variables `json:"variables,omitempty"`
	Flow      Flow      `json:"flow,omitempty"`

	ResponseCode int      `json:"response_code,omitempty"`
	Message      string   `json:"message,omitempty"`
	Logs         []string `json:"logs,omitempty"`
}

func (r *Request) GetVariable(name string) (string, bool) {
	if r.Variables == nil {
		return "", false
	}

	value, ok := r.Variables[name]
	return value, ok
}

func (r *Request) AddLog(log string) {
	r.Logs = append(r.Logs, log)
}

func (r *Request) DeserializePayload() (interface{}, error) {
	var payload interface{}
	err := json.Unmarshal([]byte(r.Payload), &payload)
	if err != nil {
		return nil, err
	}

	return payload, nil
}

type Flow struct {
	Variables Variables `json:"variables"`
}

func (f *Flow) GetVariable(name string) (string, bool) {
	if f.Variables == nil {
		return "", false
	}

	value, ok := f.Variables[name]
	return value, ok
}

// Handle
// handles a serverless request
// example payload:
//
//	{
//	  "payload": "",
//	  "meta": null,
//	  "variables": [],
//	  "flow": {
//	    "variables": []
//	  },
//	  "response_code": 0,
//	  "message": "",
//	  "logs": []
//	}
func Handle(request []byte) (string, error) {
	r := &Request{}
	err := json.Unmarshal(request, r)
	if err != nil {
		return "", fmt.Errorf("failed to deserialize request: %s", err)
	}

	if r.Variables != nil && len(r.Variables) > 0 {
		for k, v := range r.Variables {
			r.AddLog(fmt.Sprintf("variable: %s=%s", k, v))
		}
	} else {
		r.AddLog("no variables")
	}

	if r.Flow.Variables != nil && len(r.Flow.Variables) > 0 {
		for k, v := range r.Flow.Variables {
			r.AddLog(fmt.Sprintf("flow variable: %s=%s", k, v))
		}
	} else {
		r.AddLog("no flow variables")
	}

	r.ResponseCode = 12
	r.AddLog("updated response code")

	r.Variables["key1"] = "value1"
	r.Variables["key2"] = "value2"
	r.AddLog("added 2 variables (key1, key2)")

	var p interface{}
	if p, err = r.DeserializePayload(); err != nil {
		return "", fmt.Errorf("failed to deserialize payload: %s", err)
	}

	var ok bool
	var m map[string]interface{}
	if m, ok = p.(map[string]interface{}); !ok {
		r.Payload = "not a json object"
	} else {
		m["key1"] = "value1"
		m["key2"] = "value2"

		var result []byte
		result, err = json.Marshal(m)
		if err != nil {
			return "", fmt.Errorf("failed to serialize payload: %s", err)
		}

		r.Payload = string(result)
	}

	d, err := json.Marshal(r)
	if err != nil {
		return "", err
	}

	return string(d), nil
}

JavaScript

The script below demonstrates how to convert a JSON file to XML, using JavaScript.

JSON to XML

/**
  * @param data
  * @param {string} data.payload the payload as a string|null
  * @param {Object.<string, any>} data.variables any variables as key/value
  * @param {Object.<string, any>} data.meta any meta as key/value
  */

  
module.exports = async function (data) {
 var obj = JSON.parse(data.payload)
function jsonToXml(jsonObj, rootName = 'root') {
  let xml = '';

  const buildXml = (obj, parentName) => {
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const value = obj[key];
        if (typeof value === 'object') {
          xml += `<${key}>`;
          buildXml(value, key);
          xml += `</${key}>`;
        } else {
          xml += `<${key}>${value}</${key}>`;
        }
      }
    }
  };

  xml += `<?xml version="1.0" encoding="UTF-8"?>`;
  xml += `<${rootName}>`;
  buildXml(jsonObj, rootName);
  xml += `</${rootName}>`;

  return xml;
}


const xmlData = jsonToXml(obj, 'data');

const jsonString = JSON.stringify(xmlData);
  return {"payload":jsonString};
};

PHP

Converting JSON to CSV
<?php
function handle($data) {
  // Decode the JSON string into an array
  $dataArray = json_decode($data['payload'], true);
  if ($dataArray === null) {
    // JSON decoding failed
    return 'Failed to decode JSON.';
  }
  // Create an empty array to store the CSV rows
  $csvRows = [];
  // Add the CSV headers
  if (!empty($dataArray)) {
    $headers = array_keys($dataArray[0]);
    $csvRows[] = $headers;
  }
  // Add the CSV rows
  foreach ($dataArray as $row) {
    $csvRows[] = array_values($row);
  }
  // Generate the CSV string
  $csvString = '';
  foreach ($csvRows as $row) {
    $csvString .= implode(',', $row) . "\n";
  }
  // Return the CSV string or error message
  return ['payload' => !empty($csvString) ? $csvString : 'Failed to convert JSON to CSV'];
}

Python

The script below demonstrates how to convert a CSV file to JSON, using Python.

CSV to JSON
# class Request:
#     """
#     The class representing the request.

#     Attributes
#     ----------
#     payload : str
#         the payload as a string or null
#     variables : object
#         any variables as key/value
#     meta : object
#         any meta as key/value
#     """

def handle(req):
    import json, io, csv
    data = json.loads(req)
    reader = csv.DictReader(io.StringIO(data['payload']), delimiter=';')
    json_data = json.dumps(list(reader))
    return json.dumps({'payload':json_data})

Rust

The script below demonstrates how helper functions can be used, with the handle() function acting as the main driver.

Helper functions demo
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::GenericError;
use serde::de::{Deserializer, Error as SerdeError, Unexpected};

/// Custom deserializer that converts `[]` into `{}` (empty HashMap)
fn deserialize_variables<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
where
    D: Deserializer<'de>,
{
    match Value::deserialize(deserializer)? {
        Value::Object(map) => {
            let mut hashmap = HashMap::new();
            for (key, value) in map {
                if let Value::String(s) = value {
                    hashmap.insert(key, s);
                } else {
                    return Err(D::Error::custom(format!(
                        "Expected string values in HashMap, found: {:?}",
                        value
                    )));
                }
            }
            Ok(hashmap)
        }
        Value::Array(arr) if arr.is_empty() => Ok(HashMap::new()), // Convert `[]` to `{}` 
        other => Err(D::Error::invalid_type(
            Unexpected::Other(&format!("{:?}", other)),
            &"a map of strings or an empty array",
        )),
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct RequestBody {
    pub payload: Option<String>,
    pub meta: Vec<Value>,

    #[serde(default, deserialize_with = "deserialize_variables")]
    pub variables: HashMap<String, String>,

    pub flow: FlowData,

    #[serde(default)] // Default value if missing
    pub response_code: i32,

    #[serde(default)] // Default empty string if missing
    pub message: String,

    #[serde(default)] // Default empty list if missing
    pub logs: Vec<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FlowData {
    #[serde(default, deserialize_with = "deserialize_variables")]
    pub variables: HashMap<String, String>,
}



#[allow(dead_code)]
impl RequestBody {
    /// Creates a new `RequestBody` with default values
    pub fn new() -> Self {
        Self {
            payload: None,
            meta: Vec::new(),
            variables: HashMap::new(),
            flow: FlowData {
                variables: HashMap::new(),
            },
            response_code: 0,
            message: String::new(),
            logs: Vec::new(),
        }
    }

    /// Create a `RequestBody` from `Bytes`
    pub fn from_bytes(body_bytes: Bytes) -> Self {
        let body_str = String::from_utf8_lossy(&body_bytes);
        println!("Received body: {}", body_str); // Debugging output
    
        match serde_json::from_slice(&body_bytes) {
            Ok(body) => body,
            Err(err) => {
                println!("Deserialization error: {}", err); // Print exact deserialization error
                let mut default_body = Self::new();
                default_body.add_log("Failed to deserialize RequestBody; using default.");
                default_body
            }
        }
    }
    

    /// Add a log entry
    pub fn add_log(&mut self, message: &str) {
        self.logs.push(message.to_string());
    }

    /// Get the payload as a `String`
    pub fn get_payload(&self) -> Option<&String> {
        self.payload.as_ref()
    }

    /// Set a new payload
    pub fn set_payload(&mut self, payload: &str) {
        self.payload = Some(payload.to_string());
        self.add_log(&format!("Payload set to: {}", payload));
    }

    /// Deserializes the payload into a generic struct if the payload is present and valid JSON
    pub fn deserialize_payload<T: for<'de> serde::Deserialize<'de>>(&self) -> Option<T> {
        match &self.payload {
            Some(payload) if !payload.trim().is_empty() => {
                match serde_json::from_str::<T>(payload) {
                    Ok(deserialized) => Some(deserialized),
                    Err(err) => {
                        eprintln!("Failed to deserialize payload: {}", err);
                        None
                    }
                }
            }
            _ => {
                // Log explicitly that the payload was empty or null
                println!("Skipping deserialization: payload is null or empty.");
                None
            }
        }
    }   
    

    /// Serialize and update the payload from a given struct
    pub fn update_payload<T: serde::Serialize>(&mut self, new_payload: &T) {
        match serde_json::to_string(new_payload) {
            Ok(serialized) => {
                self.payload = Some(serialized);
                self.add_log("Payload updated successfully.");
            }
            Err(err) => {
                eprintln!("Failed to serialize new payload: {}", err);
                self.add_log("Failed to update payload.");
            }
        }
    }

    /// Iterate over variables
    pub fn variables_iter(&self) -> impl Iterator<Item = (&String, &String)> {
        self.variables.iter()
    }

    /// Add a variable
    pub fn add_variable(&mut self, key: &str, value: &str) {
        self.variables.insert(key.to_string(), value.to_string());
        self.add_log(&format!("Variable added: {} = {}", key, value));
    }

    /// Get a variable by key
    pub fn get_variable(&self, key: &str) -> Option<&String> {
        self.variables.get(key)
    }

    /// Add a flow variable
    pub fn add_flow_variable(&mut self, key: &str, value: &str) {
        self.flow.variables.insert(key.to_string(), value.to_string());
        self.add_log(&format!("Flow variable added: {} = {}", key, value));
    }

    /// Get a flow variable by key
    pub fn get_flow_variable(&self, key: &str) -> Option<&String> {
        self.flow.variables.get(key)
    }

    /// Clear all logs
    pub fn clear_logs(&mut self) {
        self.logs.clear();
        self.add_log("Logs cleared.");
    }

    /// Reset all variables
    pub fn reset_variables(&mut self) {
        self.variables.clear();
        self.add_log("All variables cleared.");
    }
}

#[derive(Deserialize, Serialize, Debug)]
struct CustomPayload {
    test: bool,
    name: Option<String>,
}

#[allow(unused_mut)]
pub fn handle(body_bytes: Bytes) -> Result<Bytes, GenericError> {
    // Deserialize bytes into the `RequestBody` struct
    let mut body = RequestBody::from_bytes(body_bytes);

    // Check if payload exists and is non-empty before deserializing
    if let Some(payload) = &body.payload {
        if !payload.trim().is_empty() {
            // Attempt to deserialize the payload into `CustomPayload`
            if let Some(mut custom_payload) = body.deserialize_payload::<CustomPayload>() {
                body.add_log(&format!("Deserialized payload: {:?}", custom_payload));

                // Modify the payload
                custom_payload.test = !custom_payload.test;
                custom_payload.name = Some("Updated Name".to_string());

                // Serialize the updated payload back to JSON
                body.update_payload(&custom_payload);
            } else {
                body.add_log("Failed to deserialize payload.");
            }
        } else {
            body.add_log("Payload is empty, skipping deserialization.");
        }
    } else {
        body.add_log("Payload is null, skipping deserialization.");
    }

    // Add and retrieve variables
    body.add_variable("Key1", "Value1");
    body.add_variable("Key2", "Value2");

    // Collect variables into a temporary Vec to avoid borrowing conflicts
    let variables: Vec<(String, String)> = body
        .variables_iter()
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();

    for (key, value) in variables {
        body.add_log(&format!("Iterated variable: {} = {}", key, value));
    }
    
    // Serialize the updated body back to JSON and return
    let response_body = serde_json::to_vec(&body).unwrap();
    Ok(Bytes::from(response_body))
}

Last updated