{
"name": "test",
"columns": [
{
"name": "a",
"type": "TEXT"
},
{
"name": "b",
"type": "TEXT"
}
],
"rules": [
{
"production": {
"a": "[b]"
},
"filters": {
"a": [
"",
"ALL",
false
]
},
"refcolumns": [
"b"
]
}
]
}
The JSON document has a property columns that contains a Set of Column objects (could also be a map using the property name as a key).
This is the only spot where the columns objects are fully serialized in JSON. Everywhere else, columns are referenced using the name property that is unique
Reference can be used for map keys and values
I would like to deserialize this document and :
- Resolve the references to their corresponding object in the
columnsproperty - Use the same java object instance (Column class is immutable) and not create a new
Columnevery time. (I want to reduce the number of objects)
JsonIdentityInfodoesnt work for map keys. So I use custom serializers
Here how is serialized the Rule class,
JsonColumnKeySerializer just return the "name" property of Column
class Rule {
@JsonSerialize(keyUsing = Column.JsonColumnKeySerializer.class)
private HashMap<Column, RuleFormula> productions = new HashMap<>();
@JsonSerialize(keyUsing = Column.JsonColumnKeySerializer.class)
private Map<Column, RuleFilter> filters = new LinkedHashMap<>();
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "name")
@JsonIdentityReference(alwaysAsId=true) // for testing purposes...
private Set<Column> refcolumns = new HashSet<>();
}
CodePudding user response:
I guess jackson should be able to do it for you, but i couldn't figure out how. As a workaround you can write custom deserializer, in which you can cache the results by name property:
public class CachingColumnDeserializer extends JsonDeserializer<Column> {
private static final Map<String, Column> MAP = new HashMap<>();
@Override
public Column deserialize(JsonParser parser, DeserializationContext context) throws IOException, JacksonException {
JsonNode node = parser.getCodec().readTree(parser);
String name = node.get("name").asText();
return MAP.computeIfAbsent(name, nameKey -> new Column(nameKey, node.get("type").asText()));
}
public static Map<String, Column> getMap() {
return Collections.unmodifiableMap(MAP);
}
}
We need the static instance of the map in order to share it with KeyDeserializer, getMap() returns unmodifiableMap so we can't change it by mistake. Then your KeyDeserializer will use that map to get existing instances.
public class CachedColumnKeyDeserializer extends KeyDeserializer {
private final Map<String, Column> map;
public CachedColumnKeyDeserializer() {
this.map = CachingColumnDeserializer.getMap();
}
@Override
public Object deserializeKey(String key, DeserializationContext context) throws IOException {
Column column = this.map.get(key);
if (column == null) {
return new Column(key, null);
}
return column;
}
}
Specify how to deserialize Column class
@JsonDeserialize(using = CachingColumnDeserializer.class, keyUsing = CachedColumnKeyDeserializer.class)
Just to be on the safe side you can specify you need to deserialize columns before other properties
@JsonPropertyOrder({"name", "columns", ...})
CodePudding user response:
I implemented the solution proposed here Serialize and Deserialize Map<Object, Object> Jackson
I had to crawl up to the Root parent to find the data.
Compared to the I changed the Set<Column> to a Map<String,Column>.
public class JsonColumnKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer {
@Override
public Object deserializeKey(String key, DeserializationContext context) throws IOException {
final MyRootClass root = (MyRootClass ) getRoot(context);
final Map<String, Column> columns = root.getColumns();
return columns.get(key);
}
private Object getRoot(DeserializationContext context) {
JsonStreamContext parent = context.getParser().getParsingContext().getParent();
while (parent.getParent() != null && !parent.getParent().inRoot()) {
parent = parent.getParent();
}
return parent.getCurrentValue();
}
}
