tarao / record4s

Extensible records for Scala
https://tarao.orezdnu.org/record4s/
Apache License 2.0
58 stars 3 forks source link

Integrate with jackson-databind #101

Open atty303 opened 1 week ago

atty303 commented 1 week ago

Integration with Jackson would be useful to be able to read and write YAML and TOML via jackson-dataformats-text. I'll try myself if I have time, but I'm not familiar with any of Scala3/Record4s/Jackson, so I'd be happy to implement it for those who are familiar with them. (After a little touch and go, it seemed difficult to avoid Type Erasure)

windymelt commented 2 days ago

Though I don't know well about jackson-databind, if we just want to convert % into/from YAML/TOML/JSON via jackson-databind, it seems like enough to implement ObjectMapper or Converter that maps % into jackson's internal structure.

windymelt commented 2 days ago

Supposing R <: %, because our record type R is not class itself, we should need some hack.

% scala-cli --dep com.fasterxml.jackson.core:jackson-databind:2.17.1 --dep com.github.tarao::record4s:0.13.0
Welcome to Scala 3.4.2 (17, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

scala> import com.github.tarao.record4s.%

scala> type Person = %{
     | val name: String
     | val age: Int
     | }
// defined alias type Person = com.github.tarao.record4s.%{val name: String; val age: Int}

scala> import com.fasterxml.jackson.databind.ObjectMapper

scala> val m = new ObjectMapper();
val m: com.fasterxml.jackson.databind.ObjectMapper = com.fasterxml.jackson.databind.ObjectMapper@1b26fac2

scala> val p: Person = m.readValue("""{"name":"windymelt","age":30}""", classOf[Person])
-- [E170] Type Error: ----------------------------------------------------------
1 |val p: Person = m.readValue("""{"name":"windymelt","age":30}""", classOf[Person])
  |                                                                         ^^^^^^
  |                                                Person is not a class type
  |
  | longer explanation available when compiling with `-explain`
1 error found

scala> val p: Person = %(name="Windymelt", age=30)
val p: Person = %(name = Windymelt, age = 30)

scala> p.getClass
val res0: Class[? <: Person] = class com.github.tarao.record4s.MapRecord

scala> val pp: Person = m.readValue("""{"name":"windymelt","age":30}""", p.getClass)
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.github.tarao.record4s.MapRecord` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]
  at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
  at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1887)
  at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
  at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1375)
  at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1508)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:348)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
  at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
  at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4905)
  at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3848)
  at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3816)
  ... 32 elided