code: /*
   * Copyright (c) 2022 Airbyte, Inc., all rights reserved.
   */
  
  package io.airbyte.commons.json;
  
  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
  import static org.junit.jupiter.api.Assertions.assertEquals;
  import static org.junit.jupiter.api.Assertions.assertNotSame;
  import static org.junit.jupiter.api.Assertions.assertNull;
  
  import com.fasterxml.jackson.annotation.JsonProperty;
  import com.fasterxml.jackson.core.type.TypeReference;
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.node.BinaryNode;
  import com.google.common.base.Charsets;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.Lists;
  import com.google.common.collect.Sets;
  import java.nio.charset.StandardCharsets;
  import java.util.Collections;
  import java.util.List;
  import java.util.Objects;
  import java.util.Optional;
  import org.junit.jupiter.api.Test;
  
  class JsonsTest {
  
    private static final String SERIALIZED_JSON = "{\"str\":\"abc\",\"num\":999,\"numLong\":888}";
    private static final String SERIALIZED_JSON2 = "{\"str\":\"abc\"}";
    private static final String ABC = "abc";
    private static final String DEF = "def";
    private static final String TEST = "test";
    private static final String TEST2 = "test2";
    private static final String XYZ = "xyz";
  
    @Test
    void testSerialize() {
      assertEquals(
          SERIALIZED_JSON,
          Jsons.serialize(new ToClass(ABC, 999, 888L)));
  
      assertEquals(
          "{\"test\":\"abc\",\"test2\":\"def\"}",
          Jsons.serialize(
              ImmutableMap.of(
                  TEST, ABC,
                  TEST2, DEF)));
    }
  
    @Test
    void testSerializeJsonNode() {
      assertEquals(
          SERIALIZED_JSON,
          Jsons.serialize(Jsons.jsonNode(new ToClass(ABC, 999, 888L))));
  
      assertEquals(
          "{\"test\":\"abc\",\"test2\":\"def\"}",
          Jsons.serialize(Jsons.jsonNode(ImmutableMap.of(
              TEST, ABC,
              TEST2, DEF))));
      // issue: 5878 add test for binary node serialization, binary data are serialized into base64
      assertEquals(
          "{\"test\":\"dGVzdA==\"}",
          Jsons.serialize(Jsons.jsonNode(ImmutableMap.of(
              TEST, new BinaryNode("test".getBytes(StandardCharsets.UTF_8))))));
    }
  
    @Test
    void testDeserialize() {
      assertEquals(
          new ToClass(ABC, 999, 888L),
          Jsons.deserialize("{\"str\":\"abc\", \"num\": 999, \"numLong\": 888}", ToClass.class));
    }
  
    @Test
    void testDeserializeToJsonNode() {
      assertEquals(
          SERIALIZED_JSON2,
          Jsons.deserialize(SERIALIZED_JSON2).toString());
  
      assertEquals(
          "[{\"str\":\"abc\"},{\"str\":\"abc\"}]",
          Jsons.deserialize("[{\"str\":\"abc\"},{\"str\":\"abc\"}]").toString());
      // issue: 5878 add test for binary node deserialization, for now should be base64 string
      assertEquals(
          "{\"test\":\"dGVzdA==\"}",
          Jsons.deserialize("{\"test\":\"dGVzdA==\"}").toString());
    }
  
    @Test
    void testTryDeserialize() {
      assertEquals(
          Optional.of(new ToClass(ABC, 999, 888L)),
          Jsons.tryDeserialize("{\"str\":\"abc\", \"num\": 999, \"numLong\": 888}", ToClass.class));
  
      assertEquals(
          Optional.of(new ToClass(ABC, 999, 0L)),
          Jsons.tryDeserialize("{\"str\":\"abc\", \"num\": 999, \"test\": 888}", ToClass.class));
    }
  
    @Test
    void testTryDeserializeToJsonNode() {
      assertEquals(
          Optional.of(Jsons.deserialize(SERIALIZED_JSON2)),
          Jsons.tryDeserialize(SERIALIZED_JSON2));
  
      assertEquals(
          Optional.empty(),
          Jsons.tryDeserialize("{\"str\":\"abc\", \"num\": 999, \"test}"));
    }
  
    @Test
    void testToJsonNode() {
      assertEquals(
          SERIALIZED_JSON,
          Jsons.jsonNode(new ToClass(ABC, 999, 888L)).toString());
  
      assertEquals(
          "{\"test\":\"abc\",\"test2\":\"def\"}",
          Jsons.jsonNode(
              ImmutableMap.of(
                  TEST, ABC,
                  TEST2, DEF))
              .toString());
  
      assertEquals(
          "{\"test\":\"abc\",\"test2\":{\"inner\":1}}",
          Jsons.jsonNode(
              ImmutableMap.of(
                  TEST, ABC,
                  TEST2, ImmutableMap.of("inner", 1)))
              .toString());
  
      assertEquals(
          Jsons.jsonNode(new ToClass(ABC, 999, 888L)),
          Jsons.jsonNode(Jsons.jsonNode(new ToClass(ABC, 999, 888L))));
    }
  
    @Test
    void testEmptyObject() {
      assertEquals(Jsons.deserialize("{}"), Jsons.emptyObject());
    }
  
    @Test
    void testArrayNode() {
      assertEquals(Jsons.deserialize("[]"), Jsons.arrayNode());
    }
  
    @Test
    void testToObject() {
      final ToClass expected = new ToClass(ABC, 999, 888L);
      assertEquals(
          expected,
          Jsons.object(Jsons.jsonNode(expected), ToClass.class));
  
      assertEquals(
          Lists.newArrayList(expected),
          Jsons.object(Jsons.jsonNode(Lists.newArrayList(expected)), new TypeReference<List<ToClass>>() {}));
  
      assertEquals(
          new ToClass(),
          Jsons.object(Jsons.deserialize("{\"a\":1}"), ToClass.class));
    }
  
    @Test
    void testTryToObject() {
      final ToClass expected = new ToClass(ABC, 999, 888L);
      assertEquals(
          Optional.of(expected),
          Jsons.tryObject(Jsons.deserialize(SERIALIZED_JSON), ToClass.class));
  
      assertEquals(
          Optional.of(expected),
          Jsons.tryObject(Jsons.deserialize(SERIALIZED_JSON), new TypeReference<ToClass>() {}));
  
      final ToClass emptyExpected = new ToClass();
      assertEquals(
          Optional.of(emptyExpected),
          Jsons.tryObject(Jsons.deserialize("{\"str1\":\"abc\"}"), ToClass.class));
  
      assertEquals(
          Optional.of(emptyExpected),
          Jsons.tryObject(Jsons.deserialize("{\"str1\":\"abc\"}"), new TypeReference<ToClass>() {}));
  
    }
  
    @Test
    void testClone() {
      final ToClass expected = new ToClass("abc", 999, 888L);
      final ToClass actual = Jsons.clone(expected);
      assertNotSame(expected, actual);
      assertEquals(expected, actual);
    }
  
    @Test
    void testToBytes() {
      final String jsonString = "{\"test\":\"abc\",\"type\":[\"object\"]}";
      assertArrayEquals(jsonString.getBytes(Charsets.UTF_8), Jsons.toBytes(Jsons.deserialize(jsonString)));
    }
  
    @Test
    void testKeys() {
      // test object json node
      final JsonNode jsonNode = Jsons.jsonNode(ImmutableMap.of(TEST, ABC, TEST2, DEF));
      assertEquals(Sets.newHashSet(TEST, TEST2), Jsons.keys(jsonNode));
  
      // test literal jsonNode
      assertEquals(Collections.emptySet(), Jsons.keys(jsonNode.get("test")));
  
      // test nested object json node. should only return top-level keys.
      final JsonNode nestedJsonNode = Jsons.jsonNode(ImmutableMap.of(TEST, ABC, TEST2, ImmutableMap.of("test3", "def")));
      assertEquals(Sets.newHashSet(TEST, TEST2), Jsons.keys(nestedJsonNode));
  
      // test array json node
      final JsonNode arrayJsonNode = Jsons.jsonNode(ImmutableList.of(ImmutableMap.of(TEST, ABC, TEST2, DEF)));
      assertEquals(Collections.emptySet(), Jsons.keys(arrayJsonNode));
    }
  
    @Test
    void testToPrettyString() {
      final JsonNode jsonNode = Jsons.jsonNode(ImmutableMap.of(TEST, ABC));
      final String expectedOutput = ""
          + "{\n"
          + "  \"test\": \"abc\"\n"
          + "}\n";
      assertEquals(expectedOutput, Jsons.toPrettyString(jsonNode));
    }
  
    @Test
    void testGetOptional() {
      final JsonNode json = Jsons.deserialize("{ \"abc\": { \"def\": \"ghi\" }, \"jkl\": {}, \"mno\": \"pqr\", \"stu\": null }");
  
      assertEquals(Optional.of(Jsons.jsonNode("ghi")), Jsons.getOptional(json, "abc", "def"));
      assertEquals(Optional.of(Jsons.emptyObject()), Jsons.getOptional(json, "jkl"));
      assertEquals(Optional.of(Jsons.jsonNode("pqr")), Jsons.getOptional(json, "mno"));
      assertEquals(Optional.of(Jsons.jsonNode(null)), Jsons.getOptional(json, "stu"));
      assertEquals(Optional.empty(), Jsons.getOptional(json, XYZ));
      assertEquals(Optional.empty(), Jsons.getOptional(json, ABC, XYZ));
      assertEquals(Optional.empty(), Jsons.getOptional(json, ABC, DEF, XYZ));
      assertEquals(Optional.empty(), Jsons.getOptional(json, ABC, "jkl", XYZ));
      assertEquals(Optional.empty(), Jsons.getOptional(json, "stu", XYZ));
    }
  
    @Test
    void testGetStringOrNull() {
      final JsonNode json = Jsons.deserialize("{ \"abc\": { \"def\": \"ghi\" }, \"jkl\": \"mno\", \"pqr\": 1 }");
  
      assertEquals("ghi", Jsons.getStringOrNull(json, ABC, DEF));
      assertEquals("mno", Jsons.getStringOrNull(json, "jkl"));
      assertEquals("1", Jsons.getStringOrNull(json, "pqr"));
      assertNull(Jsons.getStringOrNull(json, ABC, DEF, XYZ));
      assertNull(Jsons.getStringOrNull(json, XYZ));
    }
  
    @Test
    void testGetEstimatedByteSize() {
      final JsonNode json = Jsons.deserialize("{\"string_key\":\"abc\",\"array_key\":[\"item1\", \"item2\"]}");
      assertEquals(Jsons.toBytes(json).length, Jsons.getEstimatedByteSize(json));
    }
  
    private static class ToClass {
  
      @JsonProperty("str")
      String str;
  
      @JsonProperty("num")
      Integer num;
  
      @JsonProperty("numLong")
      long numLong;
  
      public ToClass() {}
  
      public ToClass(final String str, final Integer num, final long numLong) {
        this.str = str;
        this.num = num;
        this.numLong = numLong;
      }
  
      @Override
      public boolean equals(final Object o) {
        if (this == o) {
          return true;
        }
        if (o == null || getClass() != o.getClass()) {
          return false;
        }
        final ToClass toClass = (ToClass) o;
        return numLong == toClass.numLong
            && Objects.equals(str, toClass.str)
            && Objects.equals(num, toClass.num);
      }
  
      @Override
      public int hashCode() {
        return Objects.hash(str, num, numLong);
      }
  
    }
  
  }
  