Skip to content

Discussion: Spring ConversionService #20

Description

@micheljung

(unfortunately, GitHub discussions are not enabled in this project).

Background

We've been using MapStruct and implemented each mapper as a Spring Converter. Because of MapStruct's issues with Kotlin, some of which seem unresolvable, we're looking for an alternative and came across ShapeShift.

ShapeShift advertises with "Seamless Spring integration", but it's not that seamless; while there is a starter that automatically registers transformers and decorators, mappers still need to be registered explicitly and ShapeShift doesn't integrate well with Spring's Type Conversion (at least I wouldn't know how and it's not documented).

Issues

A Spring Converter converts from type A to type B. In ShapeShift, we have mappers and MapperTransformer that convert from one type to another. While transformers can rather easily be turned into Converter beans (but still not seamlessly), the same can't be said for mappers as there is no "Mapper" type. As a result, you can't create mapper beans and therefore you can't auto-register them.

There's also ConditionalConverter in Spring and MappingCondition in ShapeShift, but I didn't check if they integrate well.

I tried implementing some adapter code but it seemed impossible. Here's my failed attempt:

import dev.krud.shapeshift.MappingStrategy
import dev.krud.shapeshift.ShapeShift
import dev.krud.shapeshift.ShapeShiftBuilder
import dev.krud.shapeshift.spring.ShapeShiftBuilderCustomizer
import dev.krud.shapeshift.transformer.base.MappingTransformer
import dev.krud.shapeshift.transformer.base.MappingTransformerContext
import org.springframework.context.annotation.Configuration
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.Converter
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair

/**
 * Implements both, ShapeShift's [MappingTransformer] as well as Spring's [Converter]. This
 * abstracts ShapeShift away from subclasses, so that they can implement Spring's [Converter]
 * instead.
 */
abstract class MappingTransformerConverter<FROM, TO> : Converter<FROM, TO>,
  MappingTransformer<FROM, TO> {

  final override fun transform(context: MappingTransformerContext<out FROM>) =
    context.originalValue?.let { convert(it) }
}

@Configuration
class MyShapeShiftBuilderCustomizer(
  // There seems to be no way to get mapper beans
  private val mappers: Iterable<NonExistantMapperType>,
) : ShapeShiftBuilderCustomizer {
  override fun customize(builder: ShapeShiftBuilder) {
    builder
      .withDefaultMappingStrategy(MappingStrategy.MAP_ALL)

    // This is not valid code
    mappers.forEach { builder.withMapping(it) }
  }
}

/** A Spring [GenericConverter] that delegates conversion to [ShapeShift]. */
class ShapeShiftConverter(
  private val shapeShift: ShapeShift,
) : GenericConverter {

  override fun getConvertibleTypes(): Set<ConvertiblePair> {
    // There seems to be no way to get these from ShapeShift
    return emptySet()
  }

  override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any? =
    source?.let { shapeShift.map(it, targetType.objectType) }
}

What are your thoughts on this? Is this something you would like to support? Can it be done already but I just didn't see it?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions