Reputation: 1479
{
"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 :
columns
propertyColumn
every 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<>();
}
Upvotes: 0
Views: 1138
Reputation: 1479
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();
}
}
Upvotes: 0
Reputation: 6995
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", ...})
Upvotes: 1