¿Usar el constructor no predeterminado con Jerkson?

I need to serialize/deserialize a Scala class with structure something like the following:

@JsonIgnoreProperties(ignoreUnknown = true, value = Array("body"))
case class Example(body: Array[Byte]) {

    lazy val isNativeText = bodyIsNativeText
    lazy val textEncodedBody = (if (isNativeText) new String(body, "UTF-8") else Base64.encode(body))

    def this(isNativeText: Boolean, textEncodedBody: String) = this((if(isNativeText) str.getBytes("UTF-8") else Base64.decode(textEncodedBody)))

    def bodyIsNativeText: Boolean = // determine if the body was natively a string or not


It's main member is an array of bytes, which MIGHT represent a UTF-8 encoded textual string, but might not. The primary constructor accepts an array of bytes, but there is an alternate constructor which accepts a string with a flag indicating whether this string is base64 encoded binary data, or the actual native text we want to store.

For serializing to a JSON object, I want to store the body as a native string rather than a base64-encoded string if it is native text. That's why I use @JsonIgnoreProperties para no incluir la body property, and instead have a textEncodedBody that gets echoed out in the JSON.

The problem comes when I try to deserialize it like so:

val e = Json.parse[Example]("""{'isNativeText': true, 'textEncodedBody': 'hello'}""")

Recibo el siguiente error:

com.codahale.jerkson.ParsingException: Invalid JSON. Needed [body], but found [isNativeText, textEncodedBody].

Clearly, I have a constructor that will work...it just is not the default one. How can I force Jerkson to use this non-default constructor?

EDIT: I've attempted to use both the @JsonProperty y @JsonCreator annotation, but jerkson appears to disregard both of those.

EDIT2: Looking over the jerkson case class serialization source code, it looks like a case class method with the same name as its field will be used in the way that a @JsonProperty would function - that is, as a JSON getter. If I could do that, it would solve my problem. Not being super familiar with Scala, I have no idea how to do that; is it possible for a case class to have a user-defined method with the same name as one of its fields?

For reference, here is the code below that leads me to this conclusion...

private val methods = klass.getDeclaredMethods
                                .filter { _.getParameterTypes.isEmpty }
                                .map { m => m.getName -> m }.toMap

  def serialize(value: A, json: JsonGenerator, provider: SerializerProvider) {
    for (field <- nonIgnoredFields) {
      val methodOpt = methods.get(field.getName)
      val fieldValue: Object = methodOpt.map { _.invoke(value) }.getOrElse(field.get(value))
      if (fieldValue != None) {
        val fieldName = methodOpt.map { _.getName }.getOrElse(field.getName)
        provider.defaultSerializeField(if (isSnakeCase) snakeCase(fieldName) else fieldName, fieldValue, json)

preguntado el 24 de agosto de 12 a las 03:08

1 Respuestas

Correct me if I'm wrong, but it looks like Jackson/Jerkson will not support arbitrarily nested JSON. There's an ejemplo en la wiki that uses nesting, but it looks like the target class must have nested classes corresponding to the nested JSON.

Anyway, if you're not using nesting with your case classes then simply declaring a second case class and a couple implicit conversions should work just fine:

case class Example(body: Array[Byte]) {
    // Note that you can just inline the body of bodyIsNativeText here
    lazy val isNativeText: Boolean = // determine if the body was natively a string or not

case class ExampleRaw(isNativeText: Boolean, textEncodedBody: String)

implicit def exampleToExampleRaw(ex: Example) = ExampleRaw(
    if (ex.isNativeText) new String(ex.body, "UTF-8")
    else Base64.encode(ex.body)

implicit def exampleRawToExample(raw: ExampleRaw) = Example(
    if (raw.isNativeText) raw.textEncodedBody.getBytes("UTF-8")
    else Base64.decode(textEncodedBody)

Ahora debería poder hacer esto:

val e: Example = Json.parse[ExampleRaw](
  """{'isNativeText': true, 'textEncodedBody': 'hello'}"""

You could leave the original methods and annotations you added to make the JSON generation continue to work with the Example type, or you could just convert it with a cast:

generate(Example(data): ExampleRaw)


To help catch errors you might want to do something like this too:

case class Example(body: Array[Byte]) {
    // Note that you can just inline the body of bodyIsNativeText here
    lazy val isNativeText: Boolean = // determine if the body was natively a string or not
    lazy val doNotSerialize: String = throw new Exception("Need to convert Example to ExampleRaw before serializing!")

That should cause an exception to be thrown if you accidentally pass an instance of Example en lugar de ExampleRaw a una generate llamada.

Respondido 25 ago 12, 03:08

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.