All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gatling.core.json.Json.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2011-2024 GatlingCorp (https://gatling.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.gatling.core.json

import java.{ lang => jl, util => ju }
import java.io.InputStream

import scala.annotation.switch
import scala.jdk.CollectionConverters._

import io.gatling.commons.util.Hex
import io.gatling.shared.util.StringBuilderPool

import com.fasterxml.jackson.core.{ JsonFactoryBuilder, JsonToken, StreamReadConstraints, StreamReadFeature }
import com.fasterxml.jackson.core.JsonParser.NumberType._
import com.fasterxml.jackson.databind.{ JsonNode, ObjectMapper }
import com.fasterxml.jackson.databind.node.JsonNodeType._
import io.github.metarank.cfor._

private[gatling] object Json {
  private val stringBuilders = new StringBuilderPool
  private[json] val objectMapper: ObjectMapper = {
    val jsonFactoryBuilder = new JsonFactoryBuilder()
      .streamReadConstraints(StreamReadConstraints.builder.maxStringLength(Int.MaxValue).build)
      .enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
      .enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)

    try {
      jsonFactoryBuilder
        .enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER) // introduced in 2.14
        .enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER) // introduced in 2.15
    } catch {
      case _: NoSuchFieldError =>
    }

    new ObjectMapper(jsonFactoryBuilder.build)
  }

  def arrayOfObjectsLength(is: InputStream, jsonParsers: JsonParsers): Int = {
    val parser = jsonParsers.createParser(is)
    require(parser.nextToken == JsonToken.START_ARRAY, "Root element of JSON feeder file isn't an array")

    parser.nextToken match {
      case JsonToken.START_OBJECT =>
        var size = 1
        parser.skipChildren()

        while (parser.nextToken != null) {
          if (parser.getCurrentToken == JsonToken.START_OBJECT) {
            size += 1
            parser.skipChildren()
          }
        }

        size

      case JsonToken.END_ARRAY =>
        // empty array
        0

      case _ =>
        throw new IllegalArgumentException("Root element of JSON feeder file isn't an array of objects")
    }
  }

  def stringifyNode(node: JsonNode, isRootObject: Boolean): String = {
    val sb = stringBuilders.get()

    def appendStringified(node: JsonNode, rootLevel: Boolean): jl.StringBuilder = node.getNodeType match {
      case NUMBER =>
        node.numberType match {
          case INT         => sb.append(node.intValue)
          case LONG        => sb.append(node.longValue)
          case FLOAT       => sb.append(node.floatValue)
          case DOUBLE      => sb.append(node.doubleValue)
          case BIG_INTEGER => sb.append(node.bigIntegerValue)
          case BIG_DECIMAL => sb.append(node.decimalValue)
        }
      case STRING  => appendString(node.asText, rootLevel)
      case OBJECT  => appendMap(node)
      case ARRAY   => appendArray(node)
      case BOOLEAN => sb.append(node.booleanValue)
      case NULL    => sb.append("null")
      case _       => appendString(node.toString, rootLevel)
    }

    def appendString(s: String, rootLevel: Boolean): jl.StringBuilder =
      if (rootLevel) {
        appendString0(s)
      } else {
        sb.append('"')
        appendString0(s).append('"')
      }

    def appendString0(s: String): jl.StringBuilder = {
      cfor(0 until s.length) { i =>
        val c = s.charAt(i)
        c match {
          case '"'  => sb.append("\\\"")
          case '\\' => sb.append("\\\\")
          case '\b' => sb.append("\\b")
          case '\f' => sb.append("\\f")
          case '\n' => sb.append("\\n")
          case '\r' => sb.append("\\r")
          case '\t' => sb.append("\\t")
          case _ =>
            if (Character.isISOControl(c)) {
              sb.append("\\u")
              var n: Int = c
              cfor(0 until 4) { _ =>
                val digit = (n & 0xf000) >> 12
                sb.append(Hex.toHexChar(digit))
                n <<= 4
              }
            } else {
              sb.append(c)
            }
        }
      }
      sb
    }

    def appendArray(node: JsonNode): jl.StringBuilder = {
      sb.append('[')
      node.elements.asScala.foreach { elem =>
        appendStringified(elem, rootLevel = false).append(',')
      }
      if (node.size > 0) {
        sb.setLength(sb.length - 1)
      }
      sb.append(']')
    }

    def appendMap(node: JsonNode): jl.StringBuilder = {
      sb.append('{')
      node.fields.asScala.foreach { e =>
        sb.append('"').append(e.getKey).append("\":")
        appendStringified(e.getValue, rootLevel = false).append(',')
      }
      if (node.size > 0) {
        sb.setLength(sb.length - 1)
      }
      sb.append('}')
    }

    appendStringified(node, isRootObject).toString
  }

  def stringify(value: Any, isRootObject: Boolean): String = {
    val sb = stringBuilders.get()

    def appendStringified(value: Any, rootLevel: Boolean): jl.StringBuilder = value match {
      case b: Byte                   => sb.append(b)
      case s: Short                  => sb.append(s)
      case i: Int                    => sb.append(i)
      case l: Long                   => sb.append(l)
      case f: Float                  => sb.append(f)
      case d: Double                 => sb.append(d)
      case bool: Boolean             => sb.append(bool)
      case s: String                 => appendString(s, rootLevel)
      case null                      => sb.append("null")
      case map: collection.Map[_, _] => appendMap(map)
      case jMap: ju.Map[_, _]        => appendMap(jMap.asScala)
      case array: Array[_]           => appendArray(array)
      case seq: Iterable[_]          => appendArray(seq)
      case coll: ju.Collection[_]    => appendArray(coll.asScala)
      case product: Product          => appendProduct(product)
      case someObject                => sb.append(objectMapper.writeValueAsString(someObject))
    }

    def appendString(s: String, rootLevel: Boolean): jl.StringBuilder =
      if (rootLevel) {
        appendString0(s)
      } else {
        sb.append('"')
        appendString0(s).append('"')
      }

    def appendString0(s: String): jl.StringBuilder = {
      cfor(0 until s.length) { i =>
        val c = s.charAt(i)
        c match {
          case '"'  => sb.append("\\\"")
          case '\\' => sb.append("\\\\")
          case '\b' => sb.append("\\b")
          case '\f' => sb.append("\\f")
          case '\n' => sb.append("\\n")
          case '\r' => sb.append("\\r")
          case '\t' => sb.append("\\t")
          case _ =>
            if (Character.isISOControl(c)) {
              sb.append("\\u")
              var n: Int = c
              cfor(0 until 4) { _ =>
                val digit = (n & 0xf000) >> 12
                sb.append(Hex.toHexChar(digit))
                n <<= 4
              }
            } else {
              sb.append(c)
            }
        }
      }
      sb
    }

    def appendArray(iterable: Iterable[_]): jl.StringBuilder = {
      sb.append('[')
      iterable.foreach { elem =>
        appendStringified(elem, rootLevel = false).append(',')
      }
      if (iterable.nonEmpty) {
        sb.setLength(sb.length - 1)
      }
      sb.append(']')
    }

    def appendMap(map: collection.Map[_, _]): jl.StringBuilder = {
      sb.append('{')
      map.foreachEntry { (k, v) =>
        sb.append('"').append(k).append("\":")
        appendStringified(v, rootLevel = false).append(',')
      }
      if (map.nonEmpty) {
        sb.setLength(sb.length - 1)
      }
      sb.append('}')
    }

    def appendProduct(product: Product): jl.StringBuilder = {
      sb.append('{')
      cfor(0 until product.productArity) { i =>
        if (i > 0) {
          sb.append(',')
        }
        sb.append('"').append(product.productElementName(i)).append("\":")
        appendStringified(product.productElement(i), rootLevel = false)
      }
      sb.append('}')
    }

    appendStringified(value, isRootObject).toString
  }

  @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
  def asScala(node: JsonNode): Any =
    node.getNodeType match {
      case ARRAY =>
        (node.size: @switch) match {
          case 0 => Nil
          case 1 =>
            Array(asScala(node.get(0))).toSeq
          case 2 =>
            Array(
              asScala(node.get(0)),
              asScala(node.get(1))
            ).toSeq
          case 3 =>
            Array(
              asScala(node.get(0)),
              asScala(node.get(1)),
              asScala(node.get(2))
            ).toSeq
          case 4 =>
            Array(
              asScala(node.get(0)),
              asScala(node.get(1)),
              asScala(node.get(2)),
              asScala(node.get(3))
            ).toSeq
          case _ =>
            node.elements.asScala.map(asScala).toVector
        }

      case OBJECT =>
        (node.size: @switch) match {
          case 0 => Map.empty
          case 1 =>
            val entry0 = node.fields.next()
            new Map.Map1(entry0.getKey, asScala(entry0.getValue))
          case 2 =>
            val it = node.fields
            val entry0 = it.next()
            val entry1 = it.next()
            new Map.Map2(
              entry0.getKey,
              asScala(entry0.getValue),
              entry1.getKey,
              asScala(entry1.getValue)
            )
          case 3 =>
            val it = node.fields
            val entry0 = it.next()
            val entry1 = it.next()
            val entry2 = it.next()
            new Map.Map3(
              entry0.getKey,
              asScala(entry0.getValue),
              entry1.getKey,
              asScala(entry1.getValue),
              entry2.getKey,
              asScala(entry2.getValue)
            )
          case 4 =>
            val it = node.fields
            val entry0 = it.next()
            val entry1 = it.next()
            val entry2 = it.next()
            val entry3 = it.next()
            new Map.Map4(
              entry0.getKey,
              asScala(entry0.getValue),
              entry1.getKey,
              asScala(entry1.getValue),
              entry2.getKey,
              asScala(entry2.getValue),
              entry3.getKey,
              asScala(entry3.getValue)
            )
          case _ =>
            node.fields.asScala.map(e => e.getKey -> asScala(e.getValue)).toMap
        }

      case STRING  => node.textValue
      case BOOLEAN => node.booleanValue
      case NULL    => null
      case NUMBER =>
        node.numberType match {
          case INT         => node.intValue
          case LONG        => node.longValue
          case FLOAT       => node.floatValue
          case DOUBLE      => node.doubleValue
          case BIG_INTEGER => node.bigIntegerValue
          case BIG_DECIMAL => node.decimalValue
        }
      case _ => throw new IllegalArgumentException(s"Unsupported node type ${node.getNodeType}")
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy