0

I was trying to serialize/deserialize JSON using GSON. The payload in question is ApiGatewayAuthorizerContext. Inside it, there is a HashMap<String, String>. But when doing from/to json, the field naming strategy is not applied to the Keys.

@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiGatewayAuthorizerContext {

    //-------------------------------------------------------------
    // Variables - Private
    //-------------------------------------------------------------

    private Map<String, String> contextProperties = new HashMap<>();
    private String principalId;
    private CognitoAuthorizerClaims claims;
}

Same with MultiValuedTreeMap<String, String> in AwsProxyRequest class too, which is a MultivaluedMap<Key, Value>.

My field naming strategy is simple, replace - with _, for example, the payload below is not a valid JSON for many downstream components I use, and want to replace all '-', with '_'.

"MultiValueHeaders": {
    "Accept": [
        "application/json, text/plain, */*"
    ],
    "Authorization": [
        "Bearer ey...b9w"
    ],
    "Content-Type": [
        "application/json;charset=utf-8"
    ],
    "Host": [
        "aws-us-east-1-dev-dws-api.xxxxxxxx.com"
    ],
    "User-Agent": [
        "axios/0.20.0"
    ],
    "X-Amzn-Trace-Id": [
        "Root=1-xxxxxxxx-xxxxxxxxxxxxxxxx"
    ],
    "X-Forwarded-For": [
        "127.0.232.171"
    ],
    "X-Forwarded-Port": [
        "443"
    ],
    "X-Forwarded-Proto": [
        "https"
    ]
},

Any idea?

EDIT: Adding Field Naming Strategy.

public class ApiEventNamingStrategy implements FieldNamingStrategy {

  /**
   * Translates the field name into its {@link FieldNamingPolicy.UPPER_CAMEL_CASE} representation.
   *
   * @param field the field object that we are translating
   * @return the translated field name.
   */
  public String translateName(Field field) {
    String fieldName = FieldNamingPolicy.UPPER_CAMEL_CASE.translateName(field);
    if (fieldName.contains("-")) {
      fieldName = fieldName.replace('-', '_');
    }
    return fieldName;
  }
}

which is used to setFieldNamingStrategy as shown below,

  private static Gson gson =
      (new GsonBuilder()).setFieldNamingStrategy(new ApiEventNamingStrategy()).create();

The result is, all the member variables other than the ones inside the Map gets checked, and renamed. Seems setFieldNamingStrategy wont look inside a Map and rename the Keys.

Now I'm looking at the registering a TypeAdapter by utilizing registerTypeAdapterFactory. Seems the the answer by @linfaxin here gson-wont-properly-serialise-a-class-that-extends-hashmap would come to rescue! But the problem is, where/how to and/or the right place to introduce the field naming strategy in the RetainFieldMapFactory class, becasue I see a lot of avenues to hack it in.

Any ideas are most welcome!

btw, the values are populated by AWS APIGateway AND a custom authorization lambda. No way I think I could change the behavior of the APIGateway.

Jimson James
  • 2,937
  • 6
  • 43
  • 78
  • please add the missing part from the question, regarding the field naming conversion. What have you tried so far. – Panagiotis Bougioukos Apr 05 '21 at 21:41
  • Also how to you expect the `Map contextProperties` to get filled? Are you doing something that fills that map with elements? – Panagiotis Bougioukos Apr 05 '21 at 21:43
  • Correct, `contextProperties` are filled by a custom authorization lambda. It is possible to modify the `contextProperties`, but `X-Amzn-Trace-Id`, `X-Forwarded-For` etc in `MultiValueHeaders` still makes life difficult! – Jimson James Apr 05 '21 at 22:57
  • Your assumption regarding the field naming strategy is wrong because it is designed to translate _class field_ names (see the interface method declaration), not arbitrary objects (hint: the internal map type adapter factory does not have any name translation features at all). Additionally, the linked question for extending hash maps is irrelevant: it addresses the problem where the OP is trying to merge the extending class properties with the map interface Gson is aware of by default. Last, Gson is not aware of non-standard `MultiValuedTreeMap` so you have to implement a custom type adapter. – terrorrussia-keeps-killing Apr 06 '21 at 02:42

1 Answers1

0

GSON will not get inside map and consider what you want to do. Jackson either.

Considering that you already have your content in a map, I think it is much much easier to just convert the map with 3 lines of code instead of trying to hack how libraries serialize and deserialize objects.

Map<String, String> contextPropertiesNormalized= contextProperties.keySet()
                       .stream()
                       .collect(Collectors.toMap(k-> k.contains("-") ? k.replace("-","_"): k, v -> contextProperties::get));
Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
  • Yep, this is the easiest way, however sometimes intermediate maps are unwanted, thus creating a deserializer makes some sense. Also, maps in such scenarios should typically be collected out of `entries()`, not `keySet()`: it guarantees collecting entries properly even for parallel streams not referencing the source map that's not necessarily thread-safe (in general, stream functions should be side-effect free and not be bound to "outer world" context of the current lexical scope). Also, I believe you also meant `String.replaceAll()` (and then `Pattern.compile()` to speed it up with a matcher). – terrorrussia-keeps-killing Apr 06 '21 at 02:56