Searching...
sábado, 1 de abril de 2023

Aproximación en Scala a un generador de datos simulados en formato JSON

Utilizamos la librería Jackson para leer el archivo de configuración en formato JSON.

Para leer el archivo de configuración, utilizamos el objeto ObjectMapper de la librería Jackson, y registramos el módulo DefaultScalaModule para permitir la serialización y deserialización de objetos Scala.

Para generar distintas fechas o timestamps en el JSON generado, podemos utilizar la librería java.time de Java. Para ello, podemos añadir una opción adicional en el fichero de configuración que indique el rango de fechas o timestamps que queremos generar.

un ejemplo de fichero de configuración que podrías utilizar para el programa anterior:

{ "numObjects": 10, 

 "schema": { "id": "integer", "name": "string", "age": "integer", "weight": "decimal(10,2)", "birthday": "date", "timestamp": "timestamp" }, 

 "dateRange": { "start": "2022-01-01", "end": "2022-01-31" } }

En este fichero se especifica que se quieren generar 10 objetos JSON con un esquema que incluye los campos "id", "name", "age", "weight", "birthday" y "timestamp", donde "id" es un entero, "name" es una cadena de caracteres, "age" es un entero, "weight" es un decimal con 10 dígitos y 2 decimales, "birthday" es una fecha y "timestamp" es una marca de tiempo.

Además, se especifica que las fechas y marcas de tiempo deben generarse dentro del rango del mes de enero de 2022.

Por último, para generar los objetos JSON utilizamos de nuevo la librería Jackson. En este caso, creamos una instancia de ObjectMapper, y llamamos a su método writeValueAsString para serializar el objeto a formato JSON.

LISTADO DEL PROGRAMA


import java.io.File

import java.nio.charset.StandardCharsets

import java.time.format.DateTimeFormatter

import java.time.{Instant, LocalDateTime, ZoneOffset}

import java.util.Base64


import com.fasterxml.jackson.databind.ObjectMapper

import com.fasterxml.jackson.module.scala.DefaultScalaModule


import scala.util.Random


case class Config(schema: Map[String, String], numObjects: Int, dateRange: Option[(String, String)])


object JsonGenerator {


  def main(args: Array[String]): Unit = {

    require(args.length == 1, "A configuration file must be provided")

    val configFile = new File(args(0))

    require(configFile.exists(), "Configuration file does not exist")


    // Read configuration from file

    val mapper = new ObjectMapper().registerModule(DefaultScalaModule)

    val config = mapper.readValue(configFile, classOf[Config])


    // Generate objects

    val objects = (1 to config.numObjects).map(_ => generateObject(config.schema, config.dateRange))

    val json = mapper.writeValueAsString(objects)


    // Print JSON or save to file

    println(json)

  }


  def generateObject(schema: Map[String, String], dateRange: Option[(String, String)]): Map[String, Any] = {

    schema.map {

      case (name, "string") => name -> generateString()

      case (name, "int") => name -> generateInt()

      case (name, "decimal") => name -> generateDecimal()

      case (name, "date") => name -> generateDate(dateRange)

      case (name, "timestamp") => name -> generateTimestamp(dateRange)

      case (name, t) => throw new IllegalArgumentException(s"Unsupported data type $t")

    }

  }


  def generateString(length: Int = 10): String = {

    Random.alphanumeric.take(length).mkString

  }


  def generateInt(): Int = {

    Random.nextInt(1000)

  }


  def generateDecimal(): BigDecimal = {

    BigDecimal(Random.nextDouble())

  }


  def generateDate(dateRange: Option[(String, String)]): String = {

    val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")

    dateRange match {

      case Some((startStr, endStr)) =>

        val start = LocalDate.parse(startStr, formatter).toEpochDay

        val end = LocalDate.parse(endStr, formatter).toEpochDay

        val randomDay = start + Random.nextInt((end - start).toInt)

        formatter.format(LocalDate.ofEpochDay(randomDay))

      case None =>

        formatter.format(LocalDate.now())

    }

  }


def generateTimestamp(dateRange: Option[(String, String)]): String = {

  val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")

  dateRange match {

    case Some((startStr, endStr)) =>

      val start = LocalDateTime.parse(startStr + "T00:00:00.000", formatter).toEpochSecond(ZoneOffset.UTC)

      val end = LocalDateTime.parse(endStr + "T00:00:00.000", formatter).toEpochSecond(ZoneOffset.UTC)

      val randomTimestamp = start + Random.nextInt((end - start).toInt)

      formatter.format(Instant.ofEpochSecond(randomTimestamp))

    case None =>

      val now = LocalDateTime.now().toInstant(ZoneOffset.UTC)

      formatter.format(now)

  }

}

El método “generateTimestamp” recibe como parámetro un rango de fechas opcionales, representado por una tupla de dos cadenas de caracteres en formato "yyyy-MM-dd". Si se proporciona un rango de fechas, el método generará una marca de tiempo aleatoria entre el inicio y el fin del rango, en formato "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'". Si no se proporciona un rango de fechas, el método generará una marca de tiempo actual en formato "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'".

Para generar la marca de tiempo aleatoria, el método convierte primero las cadenas de fecha de inicio y fin en segundos desde la época (01-01-1970 00:00:00 UTC) utilizando el patrón de formato especificado. Luego genera un número entero aleatorio entre el número de segundos correspondientes a la fecha de inicio y la fecha de fin, y agrega este número de segundos a la época. Finalmente, formatea la marca de tiempo resultante utilizando el formateador y la clase Instant de Java 8.

Hay algunas cosas más a destacar sobre el programa anterior:

  1. Escalabilidad: El programa se puede escalar fácilmente para soportar más tipos de datos y esquemas más complejos. Por ejemplo, se podrían agregar tipos de datos adicionales como booleanos, listas y mapas, así como también esquemas que contengan relaciones entre tablas.

  2. Personalización: El programa se puede personalizar fácilmente para adaptarse a los requisitos específicos de los usuarios. Por ejemplo, se podrían agregar más parámetros de configuración, como la longitud máxima de las cadenas generadas, el rango de valores numéricos y la precisión de los valores decimales.

  3. Facilidad de uso: El programa es fácil de usar, ya que solo requiere unos pocos parámetros de entrada y no tiene dependencias externas. Esto lo hace ideal para usuarios que necesitan generar datos de prueba de forma rápida y sencilla.

  4. Aleatoriedad: La utilidad genera valores aleatorios para los objetos JSON, lo que garantiza que los datos generados sean diferentes en cada ejecución. Esto es importante para garantizar que los datos de prueba sean representativos y no estén sesgados por un conjunto de valores fijo.


    también se pueden incluir algunos tipos de datos adicionales como booleanos, arrays y estructuras. Para generar valores aleatorios para estos tipos de datos, se pueden utilizar bibliotecas como Faker o ScalaCheck.

    Además, es posible añadir más opciones de configuración para personalizar la generación de datos, como el rango de valores para los enteros y decimales, la longitud máxima de las cadenas y la distribución de probabilidad para los valores generados.

    También se puede incluir una opción para exportar los datos generados en formato JSON a un archivo en S3, para poder utilizarlos en pruebas de integración y carga de datos en sistemas como Glue o Athena.


0 comentarios:

Publicar un comentario

Gracias por participar en esta página.

 
Back to top!