I use Retrofit 2 in my project. I need to handle the errors of parsing and log the request URL in case of an error.
I want to do everything in one place. So I made a wrapper for parsing at the retrofit level.
Factory:
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.lang.reflect.Type
import javax.inject.Inject
class LogGsonConverterFactory #Inject constructor(private val factory: GsonConverterFactory) : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
val delegate: Converter<ResponseBody, *>? = factory.responseBodyConverter(type, annotations, retrofit)
return LogResponseBodyConverter(delegate ?: return null)
}
override fun requestBodyConverter(
type: Type, parameterAnnotations: Array<out Annotation>,
methodAnnotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<*, RequestBody>? = factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit)
}
Converter:
import com.google.gson.JsonSyntaxException
import okhttp3.ResponseBody
import retrofit2.Converter
class LogResponseBodyConverter<T>(private val converter: Converter<ResponseBody, T>) : Converter<ResponseBody, T> {
override fun convert(value: ResponseBody): T? {
try {
return converter.convert(value)
} catch (parseException: JsonSyntaxException) {
// Here I want to get URL and log an exception. But how to get url?
throw parseException
}
}
}
Through reflection I can do it
((Http1ExchangeCodec.ChunkedSource) ((Exchange.ResponseBodySource) ((RealBufferedSource) ((ForwardingSource) ((RealBufferedSource) ((ResponseBody.BomAwareReader) value.reader).source).source).delegate).source).delegate).url
Or with an interceptor that I can provide to the parser
import android.util.LongSparseArray
import androidx.core.util.set
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
interface LastRequestSource {
fun getLastUrl(): String?
fun getLastCode(): Int?
fun clearCache()
}
private const val INIT_CACHE_CAPACITY = 5
#ApplicationScope
class LastRequestInterceptorImpl #Inject constructor() : Interceptor, LastRequestSource {
private val urlCache = LongSparseArray<String?>(INIT_CACHE_CAPACITY)
private val codeCache = LongSparseArray<Int?>(INIT_CACHE_CAPACITY)
override fun getLastUrl(): String? = urlCache[Thread.currentThread().id]
override fun getLastCode(): Int? = codeCache[Thread.currentThread().id]
override fun clearCache() {
val threadId = Thread.currentThread().id
urlCache.remove(threadId)
codeCache.remove(threadId)
}
override fun intercept(chain: Interceptor.Chain): Response =
chain.proceed(chain.request()).also {
synchronized(this) {
val threadId = Thread.currentThread().id
urlCache[threadId] = it.request.url.toString()
codeCache[threadId] = it.code
}
}
}
Is there a more correct way to achieve the desired result?
ZoomX — Android Logger Interceptor is a great interceptor can help you to solve your problem.
Object delegateObj = readField(value, "delegate");
Object sourceObj1 = readField(delegateObj, "source");
Object sourceObj2 = readField(sourceObj1, "source");
Object sourceObj3 = readField(sourceObj2, "source");
Object sourceObj4 = readField(sourceObj3, "source");
Object sourceObj5 = readField(sourceObj4, "source");
HttpUrl url = (HttpUrl) readField(sourceObj5, "url");
Object readField(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(obj);
}
Related
I have very simple scenario. I have a listener, that listens queue:
import brave.Span
import brave.baggage.BaggageField
import brave.propagation.CurrentTraceContext
import brave.propagation.TraceContext
import brave.propagation.TraceContextOrSamplingFlags
import com.fasterxml.jackson.databind.ObjectMapper
import com.bla-bla.aggregatorzoo.api.shared.logging.updateTraceId
import com.bla-bla.content.api.provider.ContentType
import com.bla-bla.content.api.service.ESService
import com.bla-bla.shared.constants.CommonConstants
import org.apache.commons.lang3.StringUtils
import org.apache.logging.log4j.kotlin.Logging
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.aws.messaging.listener.SqsMessageDeletionPolicy
import org.springframework.cloud.aws.messaging.listener.annotation.SqsListener
import org.springframework.cloud.sleuth.BaggageInScope
import org.springframework.cloud.sleuth.Tracer
import org.springframework.messaging.handler.annotation.Headers
import org.springframework.stereotype.Component
#Component
class ESListener(
#Autowired private val esService: ESService,
#Autowired private var traceId: BaggageField,
#Autowired private var ctx: CurrentTraceContext,
#Autowired private var tracer : Tracer
) : Logging {
#Autowired
lateinit var objectMapper: ObjectMapper
#SqsListener(value = ["\${reindex.queue.show}"], deletionPolicy = SqsMessageDeletionPolicy.ALWAYS)
fun reIndexShows(message: String, #Headers headers: Map<String, String>){
processMessage(message, headers, ContentType.SHOW)
}
#SqsListener(value = ["\${reindex.queue.episodes}"], deletionPolicy = SqsMessageDeletionPolicy.ALWAYS)
fun reIndexEpisodes(message: String, #Headers headers: Map<String, String>){
processMessage(message, headers, ContentType.EPISODE)
}
private fun processMessage(message: String, headers: Map<String, String>, type: ContentType) {
try {
val currentSpan = tracer.currentSpan()
val secretBaggageField: BaggageInScope = tracer.getBaggage(CommonConstants.WS_HEADER_TRACED_ID)
val secretBaggage = if (secretBaggageField != null) secretBaggageField.get() else null
logger.info("Super secret baggage item for key [${CommonConstants.WS_HEADER_TRACED_ID}] is [$secretBaggage]" )
if (StringUtils.isNotEmpty(secretBaggage)) {
currentSpan?.event("secret_baggage_received")
currentSpan?.tag("baggage", secretBaggage)
}
val baggageKey = CommonConstants.WS_HEADER_TRACED_ID
val baggageValue = headers[CommonConstants.WS_HEADER_TRACED_ID]
val baggageField: BaggageInScope = tracer.createBaggage(baggageKey)
val context =currentSpan?.context()
baggageField.set(context, baggageValue)
currentSpan?.event("baggage_set")
currentSpan?.tag(baggageKey, baggageValue)
logger.info("Hello from service1. Calling service2")
logger.info("trace-id: ${traceId.value}")
logger.info("got message body: $message")
val traceContext: TraceContext = TraceContext.newBuilder()
.traceId(123456789)
.spanId(123456789)
.build()
val span= tracer.nextSpan(TraceContextOrSamplingFlags.create(traceContext))
.name("dummyContext").start()
// headers[CommonConstants.WS_HEADER_TRACED_ID]?.let {
// traceId.updateTraceId(span?.context())
// traceId.updateValue(it)
//
//// ThreadUtil.updateMDC(
//// hashMapOf(CommonConstants.WS_HEADER_TRACED_ID to it)
//// )
// }
logger.info("trace-id: ${traceId.value}")
logger.info("got message body: $message")
val model=objectMapper.readValue(message, SQSDetail::class.java)
model.payload?.let {
logger.info("Received new SQS message for $type and id: ${it.id}")
esService.doReindex(it.id, type)
}
} catch (e: Exception) {
throw RuntimeException("Cannot process message from SQS", e)
}
}
}
my BaggageFiled configuration:
#Bean
fun traceIdField(): BaggageField? {
return BaggageField.create(CommonConstants.WS_HEADER_TRACED_ID)
}
#Bean
fun mdcScopeDecorator(): CurrentTraceContext.ScopeDecorator? {
return MDCScopeDecorator.newBuilder()
.clear()
.add(
CorrelationScopeConfig.SingleCorrelationField.newBuilder(traceIdField())
.flushOnUpdate()
.build()
)
.build()
}
val currentSpan = tracer.currentSpan()
retruns null in ESListener. No span -> no traceContext -> No BaggageFiled to be propagated. I need to popululate the baggage to pass it else were as a bean.
I tried to create a new Span, start it, set the dummy context to Braver.Tracer and update the value of baggagefield:
headers[CommonConstants.WS_HEADER_TRACED_ID]?.let {
traceId.updateTraceId(span?.context())
traceId.updateValue(it)
}
-it does not work out. Is there any way to do it?
Actually I am able to do it using MDC map:
MDC.setContextMap(
mapOf(
CommonConstants.WS_HEADER_TRACED_ID to headers[CommonConstants.WS_HEADER_TRACED_ID]
))
But I have preference to use sleuth Api to pass over my trace-id as a #Bean.
Spring Boot Version: 2.5.1,
Spring Cloud Version: 2020.0.3
Hello guys !!!
I need your help ...
My question is that I can't modify the request body in spring gateway. Follow:
I have a MobileGatewayFilterFactory class that extends from AbstractGatewayFilterFactory where the apply method returns a custom filter: MobileGatewayFilter.
#Component
class MobileGatewayFilterFactory :
AbstractGatewayFilterFactory<MobileGatewayFilterFactory.Config>(Config::class.java),
Ordered {
override fun apply(config: Config): GatewayFilter {
logger.info { "Loading MobileGatewayFilter with config ${config.className}, ${config.execution}, ${config.custom}" }
return MobileGatewayFilter(config)
}
override fun getOrder(): Int {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1
}
data class Config(
val className: String,
val execution: String,
val custom: String?
)
}
So, inside the MobileGatewayFilter class I implement the business rules to determine which filter is running: PRE or POST filter. This is done in the filter method of the MobileGatewayFilter class where there is a condition to determine the type of decoration being executed, using reflection. If it is a request, the ServerHttpRequestDecorator is executed and a ServerHttpResponseDecorator otherwise.
class MobileGatewayFilter(private val config: MobileGatewayFilterFactory.Config) : GatewayFilter, Ordered {
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
return when (config.execution) {
"PRE" -> chain.filter(exchange.mutate().request(decoratorRequest(exchange)).build())
"POST" -> chain.filter(exchange.mutate().response(decoratorResponse(exchange)).build())
else -> chain.filter(exchange)
}
}
override fun getOrder(): Int {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1
}
private fun decoratorResponse(exchange: ServerWebExchange): ServerHttpResponse {
val aClass = Class.forName(config.className)
val obj = aClass.getConstructor(ServerHttpResponse::class.java, MobileGatewayFilterFactory.Config::class.java)
return obj.newInstance(exchange.response, config) as ServerHttpResponseDecorator
}
private fun decoratorRequest(exchange: ServerWebExchange): ServerHttpRequest {
val aClass = Class.forName(config.className)
val obj = aClass.getConstructor(ServerHttpRequest::class.java, MobileGatewayFilterFactory.Config::class.java)
return obj.newInstance(exchange.request, config) as ServerHttpRequestDecorator
}
}
Furthermore, I have a CustomerDataBodyDecorator that extends the ServerHttpRequestDecorator and overrides the getBody method. The getBody method is where the request body must be modified.
class CustomerDataBodyDecorator(
private val exchange: ServerHttpRequest,
private val config: MobileGatewayFilterFactory.Config
) : ServerHttpRequestDecorator(exchange) {
override fun getBody(): Flux<DataBuffer> {
logger.info { "getBody chamado ..." }
val body: Flux<DataBuffer> = exchange.body
var requestData = ""
body.subscribe {
val content = ByteArray(it.readableByteCount())
it.read(content)
DataBufferUtils.release(it)
requestData = String(content, Charset.forName("UTF-8"))
logger.info { "Request: $requestData" }
}
val factory = DefaultDataBufferFactory()
val buffer = factory.wrap(requestData.toByteArray())
return Flux.just(buffer)
}
}
However, the above code doesn't work because the return is executed first with empty requestData and after subscribe method is executed. I know that in Webflux the subscribe method is necessary to indicate to the publisher the information consumption needs
application.yml
id: opengw-mobile-simulation
uri: ${custom.resources.opengw}
predicates:
- Path=/opengw/v1/mobile/simulation
filters:
- name: Mobile
args:
className: br.com.decorator.CustomerDataBodyDecorator
execution: PRE
custom: ${custom.resources.customer}
- RewritePath=/opengw/v1/(?<segment>/?.*), /$\{segment}
I read several topics here but I couldn't find a solution that worked.
How can I read and then modify the request body of the Flux object in this scenario?
I used this example https://baeldung-cn.com/rest-api-search-language-spring-data-querydsl to implement the same in Kotlin.
I created an entity called Shift. Querying is working fine for the equal operators. When comes to the other operators, It throws this error.,
"java.lang.IllegalArgumentException: Unsupported target type : int\n\tat com.querydsl.core.util.MathUtils.cast(MathUtils.java:86)\n\tat com.querydsl.core.types.dsl.NumberExpression.cast(NumberExpression.java:178)\n\tat com.querydsl.core.types.dsl.NumberExpression.goe(NumberExpression.java:293)\n\tat com.presto.salesApp.common.querydsl.ShiftPredicate.getPredicate(ShiftPredicate.kt:19)\n\tat com.presto.salesApp.common.querydsl.ShiftPredicatesBuilder.build$lambda-0(ShiftPredicatesBuilder.kt:34)\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)\n\tat java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)\n\tat java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)\n\tat java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)\n\tat com.presto.salesApp.common.querydsl.ShiftPredicatesBuilder.build(ShiftPredicatesBuilder.kt:37)\n\tat com.presto.salesApp.modules.shift.ShiftController.getQueryDslShift(ShiftController.kt:166)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:655)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:834)\n",
"message": "Unsupported target type : int",
When I debugged the app, I found that an error is thrown from,
">" -> return path.goe(value)
this line of ShiftPredicate class.
This is my ShiftPredecateBuilder class
package com.presto.salesApp.common.querydsl
import com.querydsl.core.types.Predicate
import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.core.types.dsl.Expressions
import java.util.*
import java.util.stream.Collectors
class ShiftPredicatesBuilder {
// val params: List<SearchCriteria> = listOf<SearchCriteria>() ;
val params : MutableList<SearchCriteria> = ArrayList()
// fun ShiftPredicatesBuilder() {
// params = ArrayList<SearchCriteria>();
// }
fun with(
key: String, operation: String, value: Any
): ShiftPredicatesBuilder {
params.add(SearchCriteria(key, operation, value))
return this
}
fun build(): BooleanExpression {
// if (params!!.size == 0) {
// return null;
// }
//
val predicates: MutableList<BooleanExpression> = params
.stream()
.map<BooleanExpression> {
param: SearchCriteria -> ShiftPredicate(param).getPredicate()
}
.filter(Objects::nonNull)
.collect(Collectors.toList())
var result = Expressions.asBoolean(true).isTrue;
for (predicate in predicates) {
result = result.and(predicate as Predicate?)
}
return result;
}
}
This is ShiftPredicate class,
package com.presto.salesApp.common.querydsl
import com.presto.salesApp.modules.shift.Shift
import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.core.types.dsl.PathBuilder
class ShiftPredicate(paraCriteria: SearchCriteria) {
private val criteria: SearchCriteria =paraCriteria;
fun getPredicate(): BooleanExpression?
{
val entityPath: PathBuilder<Shift?> = PathBuilder<Shift?>(Shift::class.java, "shift")
if (isNumeric(criteria.value.toString())) {
val path = entityPath.getNumber(criteria.key, Int::class.java)
val value = criteria.value.toString().toInt()
when (criteria.operation) {
":" -> return path.eq(value)
">" -> return path.goe(value)
"<" -> return path.lt(value)
}
} else {
val path = entityPath.getString(criteria.key)
if (criteria.operation.equals(":", ignoreCase = true)) {
return path.containsIgnoreCase(criteria.value.toString())
}
}
return null
}
fun isNumeric(str: String): Boolean {
try {
str.toInt()
} catch (e: NumberFormatException) {
return false
}
return true
}
}
This is the controller method. I hardcoded values for the moment,
#GetMapping("/search")
fun getQueryDslShift(#RequestParam(value = "search") search: String): Any {
val builder = ShiftPredicatesBuilder().with("version",">",3)
val exp: BooleanExpression = builder.build()
return shiftService.getQueryDSLShiftByPredicate(exp)
}
This is the error,
Used this version in POM
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>5.0.0</version>
</dependency>
I faced the same issues before,
Try returning the Int class with javaObjectType, and then its working
on your code, try change this
val path = entityPath.getNumber(criteria.key, Int::class.java)
with this
val path = entityPath.getNumber(criteria.key, Int::class.javaObjectType)
I hope this helps.
Here i need to store multiple custom entities in same table in room database. This things are working in single typeconverter but when it comes in the case of multiple typeconverters then it is throwing error.
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
#Entity(tableName = "tbl_finalOrder")
#TypeConverters(ItemEntityConverter::class)
data class FinalOrdersEntity (
#PrimaryKey(autoGenerate = true)
val id:Int,
val itemEntity: ItemEntity?,
// val itemEntity: String? = null,
var quantity: String?=null,
var unitName: String?=null,
var unitId: Int?=null,
var statusOfScheme: Boolean?=null,
#TypeConverters(SchemeDetailConverter::class)
var listOfScheme: List<SchemeDetailEntity>?=null,
val child_items: String?=null,
val selectedOffer:String? = null,
var offer:String?=null
)
class ItemEntityConverter {
#TypeConverter
fun stringToItemEntity(string: String?): ItemEntity = Gson().fromJson(string,ItemEntity::class.java)
#TypeConverter
fun itemEntityToString(list: ItemEntity?): String =Gson().toJson(list)
}
class SchemeDetailConverter {
#TypeConverter
fun stringToSchemeDetailEntity(json: String?): List<SchemeDetailEntity> {
val gson = Gson()
val type: Type = object : TypeToken<List<SchemeDetailEntity?>?>() {}.type
return gson.fromJson<List<SchemeDetailEntity>>(json, type)
}
#TypeConverter
fun schemeDetailEntityToString(list: List<SchemeDetailEntity?>?): String {
val gson = Gson()
val type: Type = object : TypeToken<List<SchemeDetailEntity?>?>() {}.type
return gson.toJson(list, type)
}
}
When i run this then it ask to make typeconverter class for listOfScheme.However, there is one already for it.
error
FinalOrdersEntity.java:22: error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private java.util.List<com.myproject.database.entities.SchemeDetailEntity> listOfScheme;
Can you tell me where and what i am missing here. Thank you in advance..
data class FinalOrdersEntity (
#PrimaryKey(autoGenerate = true)
val id:Int,
#TypeConverters(ItemEntityConverter::class)
val itemEntity: ItemEntity?,
// val itemEntity: String? = null,
var quantity: String?=null,
var unitName: String?=null,
var unitId: Int?=null,
var statusOfScheme: Boolean?=null,
#TypeConverters(SchemeDetailConverter::class)
var listOfScheme: List<SchemeDetailEntity>?=null,
val child_items: String?=null,
val selectedOffer:String? = null,
var offer:String?=null
)
Check if the SchemeDetailEntity has any variable with class as the type,if yes then you should add the type converter class for that too
I looked at many examples but didn't help me solve my problem,
I have this model for mapping with JSON:
data class SomeResponse (
...
#Json(name = "dictionary")
var dictionary: HashMap<String, ArrayList<String>>? = null )
And I am using Moshi for converting to/from JSON, and when the conversion process starts an exception happen
Platform java.util.HashMap> (with no annotations) requires
explicit JsonAdapter to be registered
and this is my Moshi object:
val customDateAdapter = object : Any() {
val dateFormat: DateFormat
init {
dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"))
}
#ToJson
#Synchronized
fun dateToJson(d: Date): String {
return dateFormat.format(d)
}
#FromJson
#Synchronized
fun dateToJson(s: String): Date {
try {
val date = dateFormat.parse(s)
Timber.d("DATE_FORMATTER Did format: $date")
return date
} catch (e: ParseException) {
try {
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH)
df.timeZone = TimeZone.getTimeZone("GMT")
return df.parse(s)
} catch (e: ParseException) {
e.printStackTrace()
}
return Date()
}
}
private val stringArrayListAdapter = object : Any() {
#ToJson
#Synchronized
fun arrayListToJson(list: ArrayList<String>) : List<String> = list
#FromJson
#Synchronized
fun arrayListFromJson(list: List<String>) : ArrayList<String> = ArrayList(list)
}
val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(customDateAdapter)
.add(stringArrayListAdapter)
.build()
How to fix that problem
Moshi only supports Map. Use Map or create an Adapter for the HashMap as well.