Skip to content

Deserialization breaks with field names that are different than constructor param names #444

@Gold856

Description

@Gold856

I have an immutable class where I want to apply the Json.Creator annotation (well, it's now JsonCreator from Jackson because you support that now; thanks for that!) and my JSON field names are all capitalized, while my constructor parameter names aren't. My example is https://github.com/Gold856/allwpilib/blob/f20d271fe3bad0bbf8aed6c8e338ce10a4b06a83/wpimath/src/main/java/edu/wpi/first/math/geometry/Quaternion.java, and this generates an adapter like so:

package edu.wpi.first.math.geometry;

import io.avaje.json.JsonAdapter;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.avaje.json.PropertyNames;
import io.avaje.json.view.ViewBuilder;
import io.avaje.json.view.ViewBuilderAware;
import io.avaje.jsonb.AdapterFactory;
import io.avaje.jsonb.Jsonb;
import io.avaje.jsonb.Types;
import io.avaje.jsonb.spi.Generated;
import java.io.IOException;
import java.lang.invoke.MethodHandle;

@Generated("io.avaje.jsonb.generator")
public final class QuaternionJsonAdapter implements JsonAdapter<Quaternion>, ViewBuilderAware {

  // naming convention Match
  // m_w [double] name:m_w ignoreSerialize ignoreDeserialize
  // m_x [double] name:m_x ignoreSerialize ignoreDeserialize
  // m_y [double] name:m_y ignoreSerialize ignoreDeserialize
  // m_z [double] name:m_z ignoreSerialize ignoreDeserialize
  // getW [double] name:W ignoreDeserialize
  // getX [double] name:X ignoreDeserialize
  // getY [double] name:Y ignoreDeserialize
  // getZ [double] name:Z ignoreDeserialize
  // w [double] name:W ignoreSerialize constructor
  // x [double] name:X ignoreSerialize constructor
  // y [double] name:Y ignoreSerialize constructor
  // z [double] name:Z ignoreSerialize constructor

  private final JsonAdapter<Double> pdoubleJsonAdapter;
  private final PropertyNames names;

  public QuaternionJsonAdapter(Jsonb jsonb) {
    this.pdoubleJsonAdapter = jsonb.adapter(Double.TYPE);
    this.names = jsonb.properties("m_w", "m_x", "m_y", "m_z", "W", "X", "Y", "Z");
  }

  @Override
  public void toJson(JsonWriter writer, Quaternion _quaternion) {
    writer.beginObject(names);
    writer.name(4);
    pdoubleJsonAdapter.toJson(writer, _quaternion.getW());
    writer.name(5);
    pdoubleJsonAdapter.toJson(writer, _quaternion.getX());
    writer.name(6);
    pdoubleJsonAdapter.toJson(writer, _quaternion.getY());
    writer.name(7);
    pdoubleJsonAdapter.toJson(writer, _quaternion.getZ());
    writer.endObject();
  }

  @Override
  public Quaternion fromJson(JsonReader reader) {
    // variables to read json values into, constructor params don't need _set$ flags
    double     _val$w = 0;
    double     _val$x = 0;
    double     _val$y = 0;
    double     _val$z = 0;

    // read json
    reader.beginObject(names);
    while (reader.hasNextField()) {
      final String fieldName = reader.nextField();
      switch (fieldName) {
        case "m_w": 
          reader.skipValue();
          break;

        case "m_x": 
          reader.skipValue();
          break;

        case "m_y": 
          reader.skipValue();
          break;

        case "m_z": 
          reader.skipValue();
          break;

        case "W": 
          reader.skipValue();
          break;

        case "X": 
          reader.skipValue();
          break;

        case "Y": 
          reader.skipValue();
          break;

        case "Z": 
          reader.skipValue();
          break;

        default:
          reader.unmappedField(fieldName);
          reader.skipValue();
      }
    }
    reader.endObject();

    // direct return
    return new Quaternion(_val$w, _val$x, _val$y, _val$z);
  }

  @SuppressWarnings("unchecked")
  @Override
  public boolean isViewBuilderAware() {
    return true;
  }

  @Override
  public ViewBuilderAware viewBuild() {
    return this;
  }

  @Override
  public void build(ViewBuilder builder, String name, MethodHandle handle) {
    builder.beginObject(name, handle);
    builder.add("W", pdoubleJsonAdapter, builder.method(Quaternion.class, "getW", double.class));
    builder.add("X", pdoubleJsonAdapter, builder.method(Quaternion.class, "getX", double.class));
    builder.add("Y", pdoubleJsonAdapter, builder.method(Quaternion.class, "getY", double.class));
    builder.add("Z", pdoubleJsonAdapter, builder.method(Quaternion.class, "getZ", double.class));
    builder.endObject();
  }
}

Attempts to work around this (without Jackson annotations) with some combo of well-placed Json.Alias annotations on the constructor parameters and Json.Property on the getters and Json.Ignore on the member variables failed. Even though I have a valid way of serializing, and I have a separate, valid way of deserializing, it seems like Avaje picks one or the other, and doesn't attempt to combine them. Optimally, this works like it does in Jackson, where I can make the serialization and deserialization be in different "paths" (constructor and getter, as compared to getter and setter.)

I understand this is kind of a weird request because JSON field names being different than the constructor parameter names is kinda weird, so if you want to reject this as won't fix, feel free.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions