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

com.tencent.devops.common.api.util.JsonUtil.kt Maven / Gradle / Ivy

/*
 * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.
 *
 * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.
 *
 * A copy of the MIT License is included in this file.
 *
 *
 * Terms of the MIT License:
 * ---------------------------------------------------
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of
 * the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.tencent.devops.common.api.util

import com.fasterxml.jackson.annotation.JsonFilter
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.ser.FilterProvider
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.github.benmanes.caffeine.cache.Caffeine
import com.tencent.devops.common.api.annotation.SkipLogField
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter.ISO_DATE
import java.time.format.DateTimeFormatter.ISO_DATE_TIME
import java.time.format.DateTimeFormatter.ISO_TIME

/**
 *
 * Powered By Tencent
 */
@Suppress("TooManyFunctions")
object JsonUtil {

    private const val MAX_CLAZZ = 50000L

    /**
     * 序列化时忽略bean中的某些字段,字段需要用注解SkipLogFields声明
     *
     * @param bean 对象
     * @param   对象类型
     * @return Json字符串
     * @see SkipLogField
     */
    fun  skipLogFields(bean: T): String? {
        return try {
            beanMapperCache.get(bean.javaClass)!!.writeValueAsString(bean)
        } catch (ignored: Throwable) {
            loadMapper(bean.javaClass).writeValueAsString(bean)
        }
    }

    // 如果出现50000+以上的不同的数据类(不是对象)时。。。
    // 系统性能一定会下降,永久代区可能会OOM了,但不会是在这里引起的。所以这里限制了一个几乎不可能达到的值
    private val beanMapperCache = Caffeine.newBuilder().maximumSize(MAX_CLAZZ)
        .build, ObjectMapper> { clazz -> loadMapper(clazz) }

    private fun loadMapper(clazz: Class): ObjectMapper {
        val nonEmptyMapper = objectMapper()
        var aClass: Class<*>? = clazz // bean.javaClass
        val skipFields: MutableSet = HashSet()
        val skipLogFieldClass = SkipLogField::class.java
        while (aClass != null) {
            val fields = aClass.declaredFields
            for (field in fields) {
                val fieldAnnotation = field.getAnnotation(skipLogFieldClass) ?: continue
                if (fieldAnnotation.value.trim().isNotEmpty()) {
                    skipFields.add(fieldAnnotation.value)
                } else {
                    skipFields.add(field.name)
                }
            }
            aClass = aClass.superclass
        }
        if (skipFields.isNotEmpty()) {
            nonEmptyMapper.addMixIn(clazz, skipLogFieldClass)
            // 仅包含
            val filterProvider: FilterProvider = SimpleFilterProvider()
                .addFilter(
                    skipLogFieldClass.getAnnotation(JsonFilter::class.java).value,
                    SimpleBeanPropertyFilter.serializeAllExcept(skipFields)
                )
            nonEmptyMapper.setFilterProvider(filterProvider)
        }
        return nonEmptyMapper
    }

    private val jsonModules = mutableSetOf()

    private val objectMapper = objectMapper()

    private val jsonMapper = jsonMapper()

    private fun objectMapper(): ObjectMapper {
        return ObjectMapper().apply {
            objectMapperInit()
        }
    }

    private fun ObjectMapper.objectMapperInit() {
        registerModule(javaTimeModule())
        registerModule(KotlinModule())
        enable(SerializationFeature.INDENT_OUTPUT)
        enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
        enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
        setSerializationInclusion(JsonInclude.Include.NON_NULL)
        disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
        disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
        jsonModules.forEach { jsonModule ->
            registerModule(jsonModule)
        }
    }

    private fun jsonMapper(): JsonMapper {
        return JsonMapper.builder()
            /* 使得POJO反序列化有序,对性能会有略微影响
            *  https://github.com/FasterXML/jackson-databind/issues/3900
            * */
            .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
            .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
            .disable(MapperFeature.SORT_CREATOR_PROPERTIES_FIRST).build().apply {
                objectMapperInit()
            }
    }

    private val skipEmptyObjectMapper = ObjectMapper().apply {
        registerModule(javaTimeModule())
        registerModule(KotlinModule())
        enable(SerializationFeature.INDENT_OUTPUT)
        enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
        enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
        setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
        disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
        disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
        jsonModules.forEach { jsonModule ->
            registerModule(jsonModule)
        }
    }

    private fun javaTimeModule(): JavaTimeModule {
        val javaTimeModule = JavaTimeModule()

        javaTimeModule.addSerializer(LocalTime::class.java, LocalTimeSerializer(ISO_TIME))
        javaTimeModule.addSerializer(LocalDate::class.java, LocalDateSerializer(ISO_DATE))
        javaTimeModule.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer(ISO_DATE_TIME))
        javaTimeModule.addDeserializer(LocalTime::class.java, LocalTimeDeserializer(ISO_TIME))
        javaTimeModule.addDeserializer(LocalDate::class.java, LocalDateDeserializer(ISO_DATE))
        javaTimeModule.addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer(ISO_DATE_TIME))
        return javaTimeModule
    }

    private val unformattedObjectMapper = objectMapper().apply { disable(SerializationFeature.INDENT_OUTPUT) }

    fun getObjectMapper(formatted: Boolean = true) = if (formatted) objectMapper else unformattedObjectMapper

    /**
     * 此方法仅在系统初始化时调用,不建议在运行过程中调用
     * [subModules]子模块/类注册最佳时机是在系统初始化时调用,而不是在运行过程中
     */
    fun registerModule(vararg subModules: Module) {
        synchronized(jsonModules) {
            // 过量保护,子类过多会导致解析慢,系统业务正常情况下永远不应该达到该值,如果出现必须出错
            if (jsonModules.size < MAX_CLAZZ) {
                jsonModules.addAll(subModules)
            }
        }
        subModules.forEach { subModule ->
            objectMapper.registerModule(subModule)
            jsonMapper.registerModule(subModule)
            skipEmptyObjectMapper.registerModule(subModule)
            unformattedObjectMapper.registerModule(subModule)
        }
    }

    /**
     * 转成Json, [formatted]默认ture采用格式化方式输出
     */
    fun toJson(bean: Any, formatted: Boolean = true): String {
        if (ReflectUtil.isNativeType(bean) || bean is String) {
            return bean.toString()
        }
        return getObjectMapper(formatted).writeValueAsString(bean)!!
    }

    fun toSortJson(bean: Any): String {
        if (ReflectUtil.isNativeType(bean) || bean is String) {
            return bean.toString()
        }
        return jsonMapper.writeValueAsString(bean)!!
    }

    /**
     * 将对象转可修改的Map,
     * 注意:会忽略掉值为空串和null的属性
     */
    @Deprecated("不建议使用,建议使用toMutableMap")
    fun toMutableMapSkipEmpty(bean: Any): MutableMap {
        if (ReflectUtil.isNativeType(bean)) {
            return mutableMapOf()
        }
        return if (bean is String) {
            skipEmptyObjectMapper.readValue(bean.toString(), object : TypeReference>() {})
        } else {
            skipEmptyObjectMapper.readValue(
                skipEmptyObjectMapper.writeValueAsString(bean),
                object : TypeReference>() {}
            )
        }
    }

    /**
     * 将对象转不可修改的Map
     * 注意:会忽略掉值为null的属性
     */
    fun toMap(bean: Any): Map {
        return toMutableMap(bean)
    }

    /**
     * 将对象转不可修改的Map
     * 注意:会忽略掉值为null的属性, 不会忽略空串和空数组/列表对象
     */
    fun toMutableMap(bean: Any, skipEmpty: Boolean = false): MutableMap {
        return when {
            ReflectUtil.isNativeType(bean) -> mutableMapOf()
            bean is String -> to(bean)
            else -> to((if (skipEmpty) skipEmptyObjectMapper else getObjectMapper()).writeValueAsString(bean))
        }
    }

    /**
     * 将json转指定类型对象
     * 这个只能做简单的转换List, Map类型的,如果是自定义的类会被kotlin擦除成hashMap
     * @param json json字符串
     * @return 指定对象
     */
    fun  to(json: String): T {
        return getObjectMapper().readValue(json, object : TypeReference() {})
    }

    fun  to(json: String, typeReference: TypeReference): T {
        return getObjectMapper().readValue(json, typeReference)
    }

    fun  to(json: String, type: Class): T = getObjectMapper().readValue(json, type)

    fun  toOrNull(json: String?, type: Class): T? {
        return json?.let { self ->
            if (self.isBlank()) {
                return null
            }
            try {
                getObjectMapper().readValue(self, type)
            } catch (ignore: Exception) {
                null
            }
        }
    }

    fun  toOrNull(json: String?, typeReference: TypeReference): T? {
        return json?.let { self ->
            if (self.isBlank()) {
                return null
            }
            try {
                getObjectMapper().readValue(self, typeReference)
            } catch (ignore: Exception) {
                null
            }
        }
    }

    fun  mapTo(map: Map, type: Class): T = getObjectMapper().readValue(
        getObjectMapper().writeValueAsString(map), type
    )

    fun  anyTo(any: Any?, typeReference: TypeReference): T = getObjectMapper().readValue(
        getObjectMapper().writeValueAsString(any), typeReference
    )

    @Suppress("UNCHECKED_CAST")
    fun  Any.deepCopy(): T {
        return getObjectMapper().readValue(getObjectMapper().writeValueAsString(this), this.javaClass) as T
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy