I should preface by mentioning that I am no expert on Jackson, but I see two possible places you could integrate this type of functionality:
- Custom serializers/deserializers
- A series of POJOs to do the mapping for you
The first approach may be a bit heavy handed, but you could write a custom serializer and deserializer(or multiple. I used one of each for this example) to handle the character replacement in your key names.
For an example, I'm using some JSON that has some periods and dollar signs in it, which looks like:
{\"username.\":\"testuser\",\"id\":\"12345\",\"$role\":{\"role\":\"admin\",\"roleId$\":\"999\"}}
and will be replacing "." with "_", and "$" with "|", and then back again. I wasn't really sure how to condense this further, so excuse the long post.
Using the classes:
public class User {
private final String username;
private final String id;
private final RoleInfo role;
public User(String username, String id, RoleInfo role) {
this.username = username;
this.id = id;
this.role = role;
}
...getters omitted
}
public class RoleInfo {
private final String role;
private final String roleId;
public RoleInfo(String role, String roleId) {
this.role = role;
this.roleId = roleId;
}
...getters omitted
}
And the serializer/deserializers(assuming latest version of Jackson, and you likely want to be more careful about how Nodes are processed than I am in this example):
public class MyCustomDeserializer extends JsonDeserializer<User> {
private final String period;
private final String dollarSign;
public MyCustomDeserializer(final String period, final String dollarSign){
this.period = period;
this.dollarSign = dollarSign;
}
@Override
public User deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
final TreeNode node = p.getCodec().readTree(p);
final TextNode roleNode = (TextNode) node.get(this.dollarSign + "role").get("role");
final TextNode roleIdNode = (TextNode) node.get(this.dollarSign + "role").get("roleId" + this.dollarSign);
final String roleName = roleNode.asText();
final String roleId = roleIdNode.asText();
final RoleInfo roleInfo = new RoleInfo(roleName, roleId);
final TextNode usernameNode = (TextNode) node.get("username" + period);
final TextNode idNode = (TextNode) node.get("id");
final String username = usernameNode.asText();
final String userId = idNode.asText();
final User user = new User(username, userId, roleInfo);
return user;
}
}
public class MyCustomSerializer extends JsonSerializer<User> {
private final String period;
private final String dollarSign;
public MyCustomSerializer(final String period, final String dollarSign) {
this.period = period;
this.dollarSign = dollarSign;
}
@Override
public void serialize(User user, JsonGenerator gen,
SerializerProvider serializers) throws IOException,
JsonProcessingException {
gen.writeStartObject();
gen.writeStringField("username" + this.period, user.getUsername());
gen.writeStringField("id", user.getId());
gen.writeFieldName(this.dollarSign + "role");
gen.writeStartObject();
gen.writeStringField("role", user.getRole().getRole());
gen.writeStringField("roleId" + this.dollarSign, user.getRole()
.getRoleId());
gen.writeEndObject();
gen.writeEndObject();
}
}
Serialization and deserialization:
final String testJson = "{\"username.\":\"testuser\",\"id\":\"12345\",\"$role\":{\"role\":\"admin\",\"roleId$\":\"999\"}}";
final ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(User.class, new MyCustomDeserializer(".", "$"));
module.addSerializer(User.class, new MyCustomSerializer("_", "|"));
mapper.registerModule(module);
final User user = mapper.readValue(testJson, User.class);
printUser(user);
final String convertedJson = mapper.writeValueAsString(user);
System.out.println(convertedJson);
System.out.println("");
final ObjectMapper mapper2 = new ObjectMapper();
SimpleModule module2 = new SimpleModule();
module2.addDeserializer(User.class, new MyCustomDeserializer("_", "|"));
module2.addSerializer(User.class, new MyCustomSerializer(".", "$"));
mapper2.registerModule(module2);
final User user2 = mapper2.readValue(convertedJson, User.class);
printUser(user2);
final String json2 = mapper2.writeValueAsString(user);
System.out.println(json2);
Produces:
12345
testuser
admin
999
{"username_":"testuser","id":"12345","|role":{"role":"admin","roleId|":"999"}}
12345
testuser
admin
999
{"username.":"testuser","id":"12345","$role":{"role":"admin","roleId$":"999"}}
As I said, this is pretty heavy handed, so you could also try creating some POJOs to do the mapping for you. I'm not sure if there's a more elegant way of solving this using the various json annotations and some POJOs, but I did not see a way to both serialize and deserialize with both sets of replacement characters in one POJO with this approach. DRCB's approach in the link resulted in both properties on serialization for me.
I suppose you could also consider supporting two sets of POJOS with each reserved character in their respective annotations and transition between them as you read/write from various sources. Something like(pseudocode):
ClassA{
@JsonProperty("$user")
private String user;
}
ClassB{
@JsonProperty("#user")
private String user;
}
ClassA a = ClassA.from(classB);
Or you could combine both approaches and write custom serializers and use POJOs with multiple setter annotations for deserialization. I think the solution will vary depending on the complexity of your JSON and POJOs.