Skip to content
Anjunar/ DOCS

A small mapper

Objects to maps and back, built on AnnotationIntrospector. The same shape as json-mapper, without JSON: a practical first step when you write your own framework code.

Model

Opt in with an annotation

The class marks what the mapper may touch. Everything else stays private to it.

java
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface Exposed {}
scala
class Account { @(Exposed @field) var owner: String = null @(Exposed @field) var balance: java.math.BigDecimal = null var audit: String = "not mapped" }
Mapper

Two directions, one model

toMap reads every exposed property. fromMap creates an instance through the no-argument constructor and writes every writable property the map contains.

scala
import com.anjunar.scala.universe.TypeResolver import com.anjunar.scala.universe.introspector.AnnotationIntrospector object MiniMapper { def toMap(value: AnyRef): Map[String, Any] = val model = AnnotationIntrospector.create(TypeResolver.resolve(value.getClass), classOf[Exposed]) model.properties.map(property => property.name -> property.get(value)).toMap def fromMap[T](clazz: Class[T], values: Map[String, Any]): T = val resolved = TypeResolver.resolve(clazz) val instance = resolved.findConstructor().newInstance().asInstanceOf[AnyRef] AnnotationIntrospector.create(resolved, classOf[Exposed]).properties .filter(_.isWriteable) .foreach(property => values.get(property.name).foreach(property.set(instance, _))) instance.asInstanceOf[T] }
scala
val account = MiniMapper.fromMap(classOf[Account], Map("owner" -> "Ada", "balance" -> BigDecimal(12).bigDecimal)) println(MiniMapper.toMap(account))
Output
Map(owner -> Ada, balance -> 12)
Next steps

Where a real mapper goes further

The skeleton is the same in json-mapper; the rest is what makes it production code.

What json-mapper adds
Type conversion per propertyType, including collections through typeArguments.
Nested objects, merged in place instead of created anew.
A schema per class, found through companionInstance, with visibility rules.
Validation before every write, with paths for every violation.
Models are cached

AnnotationIntrospector.create keeps one model per class and annotation, so calling it on every request costs a map lookup. Like the type cache, that map is not synchronized; warm it at startup in a server.