header

Spring Boot y JsonNode

En ciencias computacionales, marshalling es el proceso de transformar la representación de un objeto a un formato de datos que se pueda almacenar o transmitir. En este post técnico describiremos como podemos hacer marshall y unmarshall usando Jackson, JsonNode y Spring Boot. Nota: Si quieres saber que herramientas necesitas tener instaladas en tu computadora para poder familiarizarte con Spring Boot por favor visita mi previo post: Spring Boot. Entonces ejecuta este comando desde la terminal.

spring init --dependencies=webflux,lombok --build=gradle --language=java spring-boot-json-node

Aquí está el build.gradle generado:

plugins {
  id 'org.springframework.boot' version '2.1.7.RELEASE'
  id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.jos.dem.springboot.json.node'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
  compileOnly {
    extendsFrom annotationProcessor
  }
}

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-webflux'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
  testImplementation 'io.projectreactor:reactor-test'
}

Ahora, agreguemos la dependencia Junit5:

plugins {
  id 'org.springframework.boot' version '2.1.7.RELEASE'
  id 'java'
}

apply plugin: 'io.spring.dependency-management'

def junitJupiterVersion = '5.4.1'

group = 'com.jos.dem.springboot.json.node'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
  compileOnly {
    extendsFrom annotationProcessor
  }
}

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-webflux'
  compileOnly('org.projectlombok:lombok')
  annotationProcessor('org.projectlombok:lombok')
  testImplementation('org.springframework.boot:spring-boot-starter-test'){
    exclude group: 'junit', module: 'junit'
  }
  testImplementation "org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion"
  testRuntime "org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion"
  testImplementation 'io.projectreactor:reactor-test'
  testCompile 'org.junit.platform:junit-platform-commons:1.4.0'
  testCompile 'org.junit.platform:junit-platform-launcher:1.4.0'
}

test {
  useJUnitPlatform()
}

De Json a JsonNode

Ahora, hagamos la transformación de Json a JsonNode, para poder parsear un Json necesitaremos un ObjectMapper

ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1196,\"nickname\":\"josdem\",\"email\":\"joseluis.delacruz@gmail.com\"}";
JsonNode node = mapper.readTree(json);

Por favor, considera este test case para validar la transformación a JsonNode.

@Test
@DisplayName("Validate Json to JsonNode transformation")
void shouldGetJsonNodeFromJson() throws Exception {
  log.info("Running: Validate json to json node transformation at {}", new Date());

  assertAll("node",
    () -> assertEquals(1196, node.get("id").intValue(), "Should get id"),
    () -> assertEquals("josdem", node.get("nickname").textValue(), "Should get nickname"),
    () -> assertEquals("joseluis.delacruz@gmail.com", node.get("email").textValue(), "should get email")
  );

}

De JsonNode a Bean

Después, transformemos de JsonNode a POJO

package com.jos.dem.springboot.json.node.model;

import lombok.Setter;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Person {
  private Integer id;
  private String nickname;
  private String email;
}

Y aquí está el test case:

@Test
@DisplayName("Validate JsonNode to Person transformation")
void shouldGetPersonFromJsonNode() throws Exception {
  log.info("Running: Validate json to json node transformation at {}", new Date());

  Person person = mapper.treeToValue(node, Person.class);

  assertAll("person",
    () -> assertEquals(1196, person.getId(), "Should get id"),
    () -> assertEquals("josdem", person.getNickname(), "Should get nickname"),
    () -> assertEquals("joseluis.delacruz@gmail.com", person.getEmail(), "should get email")
  );

}

De Bean a JsonNode

Anora, transformemos de POJO a JsonNode.

@Test
@DisplayName("Validate Person to JsonNode transformation")
void shouldGetJsonNodeFromPerson() throws Exception {
  log.info("Running: Validate person to json node transformation at {}", new Date());
  Person person = new Person(1196, "josdem","joseluis.delacruz@gmail.com");
  JsonNode node = mapper.valueToTree(person);

  assertAll("person",
    () -> assertEquals(1196, node.get("id").intValue(), "Should get id"),
    () -> assertEquals("josdem", node.get("nickname").textValue(), "Should get nickname"),
    () -> assertEquals("joseluis.delacruz@gmail.com", node.get("email").textValue(), "should get email")
  );

}

Parámetros a JsonNode

Finalmente, transformemos sólo argumentos a JsonNode

@Test
@DisplayName("Validate Arguments to Json Node transformation")
void shouldGetJsonNodeFromArguments() throws Exception {
  log.info("Running: Validate arguments to json node transformation at {}", new Date());
  Integer id = 1196;
  String nickname = "josdem";
  String email = "joseluis.delacruz@gmail.com";

  JsonNode node = mapper.createObjectNode();
  ((ObjectNode) node).put("id", 1196);
  ((ObjectNode) node).put("nickname", "josdem");
  ((ObjectNode) node).put("email", "joseluis.delacruz@gmail.com");

  assertAll("person",
    () -> assertEquals(1196, node.get("id").intValue(), "Should get id"),
    () -> assertEquals("josdem", node.get("nickname").textValue(), "Should get nickname"),
    () -> assertEquals("joseluis.delacruz@gmail.com", node.get("email").textValue(), "should get email")
  );

}

Aquí está el test case completo:

package com.jos.dem.springboot.json.node;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Date;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import com.jos.dem.springboot.json.node.model.Person;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JsonNodeTest {

  private JsonNode node;
  private ObjectMapper mapper = new ObjectMapper();
  private Logger log = LoggerFactory.getLogger(this.getClass());

  @BeforeEach
  void init() throws Exception {
    log.info("Getting Json Node from Json");
    String json = "{\"id\":1196,\"nickname\":\"josdem\",\"email\":\"joseluis.delacruz@gmail.com\"}";
    node = mapper.readTree(json);
  }


  @Test
  @DisplayName("Validate Json to JsonNode transformation")
  void shouldGetJsonNodeFromJson() throws Exception {
    log.info("Running: Validate json to json node transformation at {}", new Date());

    assertAll("node",
      () -> assertEquals(1196, node.get("id").intValue(), "Should get id"),
      () -> assertEquals("josdem", node.get("nickname").textValue(), "Should get nickname"),
      () -> assertEquals("joseluis.delacruz@gmail.com", node.get("email").textValue(), "should get email")
    );

  }

  @Test
  @DisplayName("Validate JsonNode to Person transformation")
  void shouldGetPersonFromJsonNode() throws Exception {
    log.info("Running: Validate json to json node transformation at {}", new Date());

    Person person = mapper.treeToValue(node, Person.class);

    assertAll("person",
      () -> assertEquals(1196, person.getId(), "Should get id"),
      () -> assertEquals("josdem", person.getNickname(), "Should get nickname"),
      () -> assertEquals("joseluis.delacruz@gmail.com", person.getEmail(), "should get email")
    );

  }

  @Test
  @DisplayName("Validate Person to JsonNode transformation")
  void shouldGetJsonNodeFromPerson() throws Exception {
    log.info("Running: Validate person to json node transformation at {}", new Date());
    Person person = new Person(1196, "josdem","joseluis.delacruz@gmail.com");
    JsonNode node = mapper.valueToTree(person);

    assertAll("person",
      () -> assertEquals(1196, node.get("id").intValue(), "Should get id"),
      () -> assertEquals("josdem", node.get("nickname").textValue(), "Should get nickname"),
      () -> assertEquals("joseluis.delacruz@gmail.com", node.get("email").textValue(), "should get email")
    );

  }

  @Test
  @DisplayName("Validate Arguments to Json Node transformation")
  void shouldGetJsonNodeFromArguments() throws Exception {
    log.info("Running: Validate arguments to json node transformation at {}", new Date());
    Integer id = 1196;
    String nickname = "josdem";
    String email = "joseluis.delacruz@gmail.com";

    JsonNode node = mapper.createObjectNode();
    ((ObjectNode) node).put("id", 1196);
    ((ObjectNode) node).put("nickname", "josdem");
    ((ObjectNode) node).put("email", "joseluis.delacruz@gmail.com");

    assertAll("person",
      () -> assertEquals(1196, node.get("id").intValue(), "Should get id"),
      () -> assertEquals("josdem", node.get("nickname").textValue(), "Should get nickname"),
      () -> assertEquals("joseluis.delacruz@gmail.com", node.get("email").textValue(), "should get email")
    );

  }

  @AfterEach
  void finish() throws Exception {
    log.info("Test execution finished");
  }

}

Trabajando con Estrúcturas Json Complejas

Consideremos esta estructura Json

{
  "batchId": "fbe07c89-ffa7-4c86-9832-5f75cf765737",
  "eventType": "EmployeeClockIn",
  "publishedAt": "2019-03-09T07:36:43-05:00",
  "messages" : [{
  	"messageId" : "4aeaa175-e46d-42eb-83d3-cd02865d4863",
  	"eventAt": "2019-03-09T07:36:43-05:00",
  	"data": {
  		"firstname": "Jose",
  		"lastname": "Morales",
  		"nickname": "josdem",
  		"employeeNumber": 1196,
  		"email": "joseluis.delacruz@gmail.com",
  		"clockInDateTime": "2019-03-09T07:36:43-05:00"
  	}
  }]
}

Este podría una representación en Objeto, Event:

package com.jos.dem.springboot.json.node.model;

import java.time.OffsetDateTime;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Event {
  private String batchId;
  private String eventType;
  private OffsetDateTime publishedAt;
  private Message[] messages;
}

Este podría ser nuestra representación Objecto Message:

package com.jos.dem.springboot.json.node.model;

import java.time.OffsetDateTime;

import lombok.Getter;
import lombok.Setter;

import com.fasterxml.jackson.databind.JsonNode;

@Getter
@Setter
public class Message {
  private String messageId;
  private OffsetDateTime eventAt;
  private JsonNode data;
}

Así tenemos que para hacer unmarshall de esta estrúctura Json compleja la mejor estrategía es crear un servicio para delegar la responsabilidad.

package com.jos.dem.springboot.json.node.service;

import java.io.File;
import java.io.IOException;

import com.jos.dem.springboot.json.node.model.Event;

public interface UnmarshallerService {

  Event read(File jsonFile) throws IOException;

}

Aquí está la implementación de nuestro servicio:

package com.jos.dem.springboot.json.node.service.impl;

import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;

import javax.annotation.PostConstruct;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import org.springframework.stereotype.Service;

import com.jos.dem.springboot.json.node.model.Event;
import com.jos.dem.springboot.json.node.service.UnmarshallerService;

@Service
public class UnmarshallerServiceImpl implements UnmarshallerService {

  private ObjectMapper mapper = new ObjectMapper();

  @PostConstruct
  public void setup() {
    mapper.registerModule(new JavaTimeModule());
  }

  public Event read(File jsonFile) throws IOException {
    InputStream inputStream = new FileInputStream(jsonFile);
    JsonNode jsonNode = mapper.readTree(inputStream);
    return mapper.treeToValue(jsonNode, Event.class);
  }

}

Aquí está nuestro test case completo para validar el unmarshall:

package com.jos.dem.springboot.json.node;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Date;
import java.time.OffsetDateTime;

import com.fasterxml.jackson.databind.JsonNode;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;

import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.beans.factory.annotation.Autowired;

import com.jos.dem.springboot.json.node.model.Event;
import com.jos.dem.springboot.json.node.model.Message;
import com.jos.dem.springboot.json.node.service.UnmarshallerService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class UnmarshallerServiceTest {

  private Logger log = LoggerFactory.getLogger(this.getClass());

  @Autowired
  private UnmarshallerService service;

  private Event event;
  private Message message;
  private Message[] messages;

  @BeforeEach
  void init() throws Exception {
    log.info("Getting Event from ClockIn json file");
    File jsonFile = new File("src/main/resources/ClockIn.json");
    event = service.read(jsonFile);
    Message[] messages = event.getMessages();
    message = messages[0];
  }

  @Test
  @DisplayName("Validate Event values from ClockIn Json file")
  void shouldGetEventFromClockInFile() throws Exception {
    log.info("Running: Validate Event values from ClockIn Json file at {}", new Date());

    assertAll("event",
      () -> assertEquals("fbe07c89-ffa7-4c86-9832-5f75cf765737", event.getBatchId(), "Should get batch id"),
      () -> assertEquals("EmployeeClockIn", event.getEventType(), "Should get event type"),
      () -> assertEquals(OffsetDateTime.parse("2019-03-09T12:36:43Z"), event.getPublishedAt(), "Should get published at time")
    );

  }

  @Test
  @DisplayName("Validate Message values from Event")
  void shouldGetMessageFromEvent() throws Exception {
    log.info("Running: Validate Message values from event at {}", new Date());

    assertAll("message",
      () -> assertEquals("4aeaa175-e46d-42eb-83d3-cd02865d4863", message.getMessageId(), "Should get message id"),
      () -> assertEquals(OffsetDateTime.parse("2019-03-09T12:36:43Z"), event.getPublishedAt(), "Should get published at time")
    );

  }

  @Test
  @DisplayName("Validate Data values from message")
  void shouldGetDataFromMessage() throws Exception {
    log.info("Running: Validate Data values from message at {}", new Date());

    JsonNode data = message.getData();
    assertAll("data",
      () -> assertEquals("Jose", data.get("firstname").textValue(), "Should get Firstname"),
      () -> assertEquals("Morales", data.get("lastname").textValue(), "Should get Lastname"),
      () -> assertEquals("josdem", data.get("nickname").textValue(), "Should get Nickname"),
      () -> assertEquals(1196, data.get("employeeNumber").intValue(), "Should get Employee Number"),
      () -> assertEquals("joseluis.delacruz@gmail.com", data.get("email").textValue(), "Should get Email"),
      () -> assertEquals("2019-03-09T07:36:43-05:00", data.get("clockInDateTime").textValue(), "Should get clockIn time")
    );

  }

  @AfterEach
  void finish() throws Exception {
    log.info("Test execution finished");
  }

}

Usando Maven

Tú puedes hacer lo mismo usando Maven, la única diferencia es que tienes que específicar el parámetro --build=maven en el comando spring init:

spring init --dependencies=webflux,lombok --build=maven --language=java spring-boot-json-node

Este es el pom.xml generado con la dependencia Junit5 agregada manualmente:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.jos.dem.springboot</groupId>
  <artifactId>jsonNode</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>spring-boot-json-node</name>
  <description>This project shows how to work with Jackson JsonNode</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath/>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.surefire.version>2.22.0</maven.surefire.version>
    <maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version>
    <java.version>11</java.version>
    <junit.jupiter.version>5.4.1</junit.jupiter.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-commons</artifactId>
      <version>1.4.0</version>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-launcher</artifactId>
      <version>1.4.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

Para correr el proyecto con Gradle:

gradle test

Para correr el proyecto con Maven:

mvn test

Para explorar el proyecto, por favor ve aquí, para descargar el proyecto:

bash git clone git@github.com:josdem/spring-boot-json-node.git `

Return to the main article

comments powered by Disqus