Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate fields with ScalaObjectMapper #267

Open
reverofevil opened this issue Jun 22, 2016 · 6 comments
Open

Duplicate fields with ScalaObjectMapper #267

reverofevil opened this issue Jun 22, 2016 · 6 comments

Comments

@reverofevil
Copy link

When there is a case class with BeanProperty-annotated field, the following code sometimes generates JSON with duplicate fields. For example, pId field gets serialized twice, as pId and pid.

private val mapper = {
  val objectMapper = new ObjectMapper with ScalaObjectMapper
  objectMapper.registerModule(DefaultScalaModule)
  objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
  objectMapper
}

def toJson[A <: AnyRef](a: A): String = {
  mapper.writeValueAsString(a)
}

It's impossible to remove BeanProperty annotation, because it's needed by another library. Also it's impossible to set BeanProperty on every field and remove ScalaObjectMapper, as such changes are numerous and would likely result in other compatibility issues. What's the proper way to fix this problem?

@reverofevil
Copy link
Author

reverofevil commented Jun 22, 2016

Some strange code that may be related in ScalaPropertiesCollector

def findNameForSerialization(prop: PropertyDescriptor): Option[PropertyName] = {
  ...
  val paramName = annotatedParam.optMap(ai.findNameForDeserialization(_))
  val fieldName = annotatedField.optMap(ai.findNameForSerialization(_))
  ...
}

@christophercurrie
Copy link
Member

Can you provide an example of the case class that is causing problems?

@reverofevil
Copy link
Author

@christophercurrie I better provide a complete example.

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import scala.beans.BeanProperty

case class Test(@BeanProperty pId: Long)

val mapper = {
  val objectMapper = new ObjectMapper with ScalaObjectMapper
  objectMapper.registerModule(DefaultScalaModule)
  objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
  objectMapper
}

println(mapper.writeValueAsString(Test(1L)))

Result is

{"pId":1,"pid":1}

@pjfanning
Copy link
Member

pjfanning commented Sep 3, 2021

This appears to happen only when scala.beans.BeanProperty annotation is added - ScalaObjectMapper is not the cause (a plain ObjectMapper has same result) -- nor does setSerializationInclusion have any effect

@pjfanning
Copy link
Member

pjfanning commented Sep 3, 2021

case class Test(@BeanProperty pId: Long)

@cowtowncoder the effect of the Scala @BeanProperty annotation (not to be confused with the jackson BeanProperty annotation) is that it adds a Java Bean style getPId() method to the class. Note the case, the initial p in the field name (pId) is uppercased while the other chars retain their case. legacyManglePropertyName in DefaultAccessorNamingStrategy in jackson-databind converts getPId() to a field name called 'pid' (all lowercase) which conflicts with the still accessible method pId (the original name of the scala field).

Is there a reason why legacyManglePropertyName in DefaultAccessorNamingStrategy lowercases the 'I' in getPId()?

Enabling MapperFeature.USE_STD_BEAN_NAMING has the opposite problem - getPId() evaluates to PId instead.

@cowtowncoder
Copy link
Member

So: name mangling is to do what Java Bean specification does, to derive property name from getter/setter.
Originally ("legacy") I had misunderstood it so that the leading upper-case letters would always be lower-cased so that:

getURL() -> url (and getPId() -> pid)

but what Bean naming actually does is that lower-casing is only applied if there is one upper-case character; otherwise name is to be left as-is. Hence

getURL() -> URL (and getPId() -> PId)

However: field names are never mangled in any way. At least they should not.

The fundamental issue is deeper however: Scala (and Kotlin) start with property as a concept, matching field; then create accessors. But name mapping cannot be guaranteed as bijective (so that you can losslessly translate in either direction).
Because of this, Jackson's attempts to unify logical property name fails.

It should not be impossible to reconcile these but... it has been tricky so far.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants