0

I have a class with the following attributes,

public AnalyticsEventProperty(String eventID, String key, Object value, EventPropertyValueType valueType) {
        this.eventID = eventID;
        this.key = key;
        this.value = value;
        this.type = valueType();
}

This object is created and passed to an array of event properties, when I do the Json Conversion I get the output below:

{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","name":"app_start","propertyArrayList":[{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","key":"session_id","value":"69200430-95a0-4e14-9a36-67942917573d"}

I am getting 'key and 'value' used, I can see why, but how do I use the key and values as key and values i.e. "session_id":"69200430-95a0-4e14-9a36-67942917573d", bearing in mind that these key and values may have different property names depending on what is passed in the constructor.

When i create the String i am simply calling

String text_to_send = new Gson().toJson(events);

Where events is the ArrayList.

user15348067
  • 43
  • 1
  • 5
  • Are you expecting a JSON `Map` where for every event the key is mapped to the value (and all other attributes are ignored), or do you expect that the event objects still have their other properties and only `key` and `value` are changed? – Marcono1234 Dec 14 '21 at 00:54
  • Yes thank you, I need the event objects to have the eventID, the type as they stand they map OK as the attribute names are the keys, and then as you suggested just the key values replaced with the passed values. – user15348067 Dec 14 '21 at 10:00

1 Answers1

0

You can solve this by writing a custom TypeAdapterFactory which obtains the default adapter for your class (that is the reflection based one) and uses it to create an in-memory JSON representation in the form of a JsonObject. That JsonObject can then be modified to have the structure you expect; afterwards it has to be written to the JsonWriter:

class RewritingEventPropertyAdapterFactory implements TypeAdapterFactory {
  public static final RewritingEventPropertyAdapterFactory INSTANCE = new RewritingEventPropertyAdapterFactory();

  private RewritingEventPropertyAdapterFactory() {}

  @Override
  public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    // Only consider AnalyticsEventProperty or subtypes
    if (!AnalyticsEventProperty.class.isAssignableFrom(type.getRawType())) {
      return null;
    }

    TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
    TypeAdapter<JsonObject> jsonObjectAdapter = gson.getAdapter(JsonObject.class);

    return new TypeAdapter<T>() {
      @Override
      public T read(JsonReader in) throws IOException {
        throw new UnsupportedOperationException("Deserialization is not supported");
      }

      @Override
      public void write(JsonWriter out, T value) throws IOException {
        if (value == null) {
          out.nullValue();
          return;
        }

        JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();

        // Remove "key" and "value"
        String eventKey = jsonObject.remove("key").getAsString();
        JsonElement eventValue = jsonObject.remove("value");

        // Add back an entry in the form of `"key": "value"`
        jsonObject.add(eventKey, eventValue);

        // Write the transformed JsonObject
        jsonObjectAdapter.write(out, jsonObject);
      }
    };
  }
}

You then have to register the factory with a GsonBuilder.

An alternative would be to perform the complete serialization of the class manually by directly writing the properties to the JsonWriter. This will most likely be a bit more performant, but is also more error-prone.

Marcono1234
  • 5,856
  • 1
  • 25
  • 43