I need to deserialize some json which can contain either an array of objects [{},{}] or a single object {}. See my question. Here is what I'm trying to do :
public class LocationDeserializer extends JsonDeserializer<List<Location>>{
#Override
public List<Location> deserialize(JsonParser jp,
DeserializationContext ctxt) throws IOException
{
List<Location> list = new ArrayList<Location>();
if(!jp.isExpectedStartArrayToken()){
list.add(...);
}else{
//Populate the list
}
return list;
}
But I'm getting stuck here. How can I remap the object? And how to tell Jackson to use this deserializer for the attribute "location"?
Here is how the Json can look :
{
"location":
[
{
"code":"75",
"type":"1"
},
{
"code":"77",
"type":"1"
}
]
}
or
{
"location":
{
"code":"75",
"type":"1"
}
}
You can tell Jackson to use this deserializer with the Annotation JsonDeserialize.
And inside your deserialize method, you could use the following:
#Override
public List<Location> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
List<Location> list = new ArrayList<Location>();
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jp);
if(root.get("location").isArray()){
// handle the array
}else{
// handle the single object
}
return list;
}
I don't know what your JSON looks like, but I think using ObjectNode is a lot easier for this case than using JsonDeserializer. Something like this:
ObjectNode root = mapper.readTree("location.json");
if (root.getNodeType() == JsonNodeType.ARRAY) {
//Use a get and the JsonNode API to traverse the tree to generate List<Location>
}
else {
//Use a get and the JsonNode API to traverse the tree to generate single Location or a one-element List<Location>
}
Related
I have a JSON object that one of the a attributes is a JSON string.
a = {
"dt" : "2022-01-02 00:00:00"
"fx": "{\"id\":1,\"faixaId\":1,\"speedAtivo\":true}",
"hash": "8c91a61a0a49b73de2fc13caed00e6a93dbe435b354216802da0dbe8bfda3300",
}
In JavaScript, I can convert the "fx" attribute to an object using:
a.fx = JSON.parse(a.fx)
And the new JSON:
a = {
"dt" : "2022-01-02 00:00:00"
"fx": {"id":1,
"faixaId":1,
"speedAtivo":true
},
"hash": "8c91a61a0a49b73de2fc13caed00e6a93dbe435b354216802da0dbe8bfda3300",
}
There is a way to do this with Java?
Yes, you can parse JSON string using one of these libraries
to use these library check the docs and
maven dependency
But make sure that your JSON is in correct format as the above JSON is missing comma after the first line ending.
Below is the simple example to parse a JSON String using the above library.
String jsonStr = "{\"dt\":\"2022-01-02 00:00:00\",
\"fx\":\"id\":1,\"faixaId\":1,\"speedAtivo\":true},
\"hash\":\"8c91accsiamkFVXtw6N7DnE3QtredADYBYU35b354216802da0dbe8bfda3300\",
}";
JSONObject strObj = JSONObject.fromObject(jsonStr);
Output:
{
"dt": "2022-01-02 00:00:00",
"fx": {
"id": 1,
"faixaId": 1,
"speedAtivo": true
},
"hash":
"8c91accsiamkFVXtw6N7DnE3QtredADYBYU35b354216802da0dbe8bfda3300"
}
If you use Jackson lirary to convert objects from JSON String to objects via custom deserializer.
First you create Classes that will represent the main object and fx object
public class AClass {
public String dt;
#JsonDeserialize(using = Deserializer.class)
public FxClass fx;
public String hash;
}
public class FxClass {
public int id;
public int faixaId;
public boolean speedAtivo;
}
Then you create deserizalizer
public class Deserializer extends StdDeserializer<FxClass> {
protected Deserializer(Class<?> vc) {
super(vc);
}
protected Deserializer() {
this(null);
}
#Override
public FxClass deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
TextNode treeNode = jsonParser.getCodec().readTree(jsonParser);
return new ObjectMapper().readValue(treeNode.asText(), FxClass.class);
}
}
And add deserializer to ObjectMapperConfiguration
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(FxClass.class, new Deserializer());
mapper.registerModule(module);
And thats it. To convert JsonString to object
AClass readValue = new ObjectMapper.readValue(json, AClass.class);
For additional info try reading https://www.baeldung.com/jackson-deserialization
I am wondering how to configure Jackson to deserialize a serialized list of objects that were contained in 'special' collections (immutable, or the result of collected stream).
Here is my problem, for certain reasons, I need to declare de typing in jackson because I need to be able to deserialize everything and get back all my types.
public static class Sink {
private List items;
public Sink setItems(final List items) {
this.items = items;
return this;
}
public List getItems() {
return this.items;
}
}
public static void main(String[] args) throws Exception {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new Jdk8Module());
mapper.registerModule(new AfterburnerModule());
mapper.enableDefaultTyping(NON_FINAL, JsonTypeInfo.As.PROPERTY);
String content = mapper.writeValueAsString(new Sink().setItems(List.of()));
System.out.println(content);
Sink result = mapper.readValue(content, Sink.class);
}
But this has a nasty side effect with collections.
The content value is:
{
"#class": "api.Sink",
"items": ["java.util.ImmutableCollections$ListN", []]
}
And unfortunately, the java.util.ImmutableCollections.* do not have default constructors (which make sense).
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance ofjava.util.ImmutableCollections$ListN(no Creators, like default construct, exist): no default no-arguments constructor found
I am wondering how to force jackson to deserialize those kind of object as ArrayList or whatever type of list.
I'd like to configure it to deserialize all sub types of list as ArrayList / LinkedList or when serializing to change the type of a collection
(java 12 / jackson 2.9.7)
You can dot it using a specific Deserializer as follow for example with Mixin.
An example of the deserializer:
class UnmodifiableSetDeserializer extends JsonDeserializer<Set> {
#Override
public Set deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode node = mapper.readTree(jp);
Set<Object> resultSet = new HashSet<Object>();
if (node != null) {
if (node instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) node;
Iterator<JsonNode> nodeIterator = arrayNode.iterator();
while (nodeIterator.hasNext()) {
JsonNode elementNode = nodeIterator.next();
resultSet.add(mapper.readValue(elementNode.traverse(mapper), Object.class));
}
} else {
resultSet.add(mapper.readValue(node.traverse(mapper), Object.class));
}
}
return Collections.unmodifiableSet(resultSet);
}
}
You can find a complete example in the CoreJacksont2Module of Spring security here
2.13.0 fixes it (except Set.of() apparently?):
public static class Sink {
private List<Integer> items;
public Sink setItems(final List<Integer> items) {
this.items = items;
return this;
}
public List<Integer> getItems() {
return this.items;
}
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = JsonMapper.builder()
.findAndAddModules()
.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, EVERYTHING, JsonTypeInfo.As.PROPERTY)
.build();
String content = mapper.writeValueAsString(new Sink().setItems(List.of(1)));
System.out.println(content);
Sink result = mapper.readValue(content, Sink.class);
}
Output:
{
"#class" : "api.Sink",
"items" : [ "java.util.ImmutableCollections$List12", [ ] ]
}
I have a class that extends JsonDerserializer<Type>. In this deserialiser I have a concept of doing replacement values which I currently set using objectReader(Injectables). My problem is that sometimes I don't have injectables.
I don't see a method on ObjectMapper that allows me to check if an injectable key is set. I only see findInjectableValue which if the value isn't there it throws an InvalidDefinitionException. I am currently try catching this call which works, but I feel it is more of a hack.
Is there something I am missing?
I really don't want to have this try-catch. I want to first check if injectable value exists.
try {
Object replacementValueObject = ctxt.findInjectableValue("replacementValues", null, null);
if (replacementValueObject instanceof Map) {
replacementValues = (Map<String, Object>) replacementValueObject;
mapper.setInjectableValues(new InjectableValues.Std().addValue("replacementValues", replacementValues));
}
}catch (InvalidDefinitionException ie){
logger.info("No replacement values exist. Ignoring and moving on");
}
Right now (07-2019), there is no method to check whether value exists or not. You can only addValue and try to get it by invoking findInjectableValue.
You can create empty Map and initialise InjectableValues when ObjectMapper is created:
InjectableValues.Std injectableValues = new InjectableValues.Std();
injectableValues.addValue("replacementValues", Collections.emptyMap());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setInjectableValues(injectableValues);
or create nice method which hides complex logic behind scene:
class ReplaceMapValueJsonDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) {
return null;
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException {
Map replacementValues = getCurrentReplacementValues(ctxt);
//...
return super.deserialize(p, ctxt, intoValue);
}
private Map<String, Object> getCurrentReplacementValues(DeserializationContext ctxt) {
try {
Object value = ctxt.findInjectableValue("replacementValues", null, null);
return (Map<String, Object>) value;
} catch (JsonMappingException ie) {
return Collections.emptyMap();
}
}
}
See also:
How to use injection with XmlMapper deserialization
Given
public class ConstraintMatch {
protected String constraintName;
protected Score score;
...
}
I have the following serializer in Jackson:
public class ConstraintMatchJacksonJsonSerializer extends JsonSerializer<ConstraintMatch> {
#Override
public void serialize(ConstraintMatch constraintMatch, JsonGenerator generator, SerializerProvider serializers)
throws IOException {
generator.writeStartObject();
generator.writeStringField("constraintName", constraintMatch.getConstraintName());
generator.writeFieldName("score");
// Delegate to serialization to the default Score serializer
serializers.findValueSerializer(Score.class)
.serialize(constraintMatch.getScore(), generator, serializers);
generator.writeEndObject();
}
}
How do I write a deserializer that also delegates to the default deserializer?
public class ConstraintMatchJacksonJsonDeserializer extends JsonDeserializer<ConstraintMatch> {
#Override
public ConstraintMatch deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode tree = parser.readValueAsTree();
String constraintName = tree.get("constraintName").asText();
JsonNode scoreNode = tree.get("score");
Score score = ...; // How do I delegate to the default deserializer?
return new ConstraintMatch(constraintName, score);
}
}
I've looked at findContextualValueDeserializer() etc, but I can't create a BeanProperty instance.
In a similar situation, I actually found there were two problems to solve. Firstly, as you say, the need to delegate back to the normal deserializer. But the other problem I encountered was how to feed the JsonNode (TreeNode below) into that next deserialize(JsonParser, ...).
The following is a working sample from that situation, where I wanted to do a lookahead to figure out the subclass.
Hopefully the node here is your scoreNode. And it sounds like objectClass is just Score.class for you.
#Override
public T deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
TreeNode node = parser.readValueAsTree();
// Select the subclass to deserialize as
Class<? extends T> objectClass = deduceClass(node);
// This based on ObjectMapper._convert()
// - the problem here was the JsonParser (parser) had gone past the current node
TokenBuffer buf = new TokenBuffer(mapper, false);
SerializationConfig config = mapper.getSerializationConfig()
.without(SerializationFeature.WRAP_ROOT_VALUE);
DefaultSerializerProvider serializerProvider = ((DefaultSerializerProvider) mapper
.getSerializerProvider()).createInstance(config,
mapper.getSerializerFactory());
serializerProvider.serializeValue(buf, node);
JsonParser nestedParser = buf.asParser();
nestedParser.nextToken();
JsonDeserializer<Object> deserializer = ctxt
.findRootValueDeserializer(
mapper.getTypeFactory().constructType(objectClass));
#SuppressWarnings("unchecked")
T obj = (T) deserializer.deserialize(nestedParser, ctxt);
return obj;
}
(Just in case, this was with Jackson 2.7.9)
I'd be pleased to hear about a simpler way to create a JsonParser from a node.
Serializing this:
constraintMatch.getConstraintPackage());
generator.writeStringField("constraintName", constraintMatch.getConstraintName());
generator.writeFieldName("score");
// Delegate to PolymorphicScoreJacksonJsonSerializer
JsonSerializer<Object> scoreSerializer = serializers.findValueSerializer(Score.class);
scoreSerializer.serialize(constraintMatch.getScore(), generator, serializers);
generator.writeEndObject();
Can be deserialized with this:
parser.nextToken();
if (!"constraintName".equals(parser.getCurrentName())) {
throw new IllegalStateException(...);
}
parser.nextToken();
String constraintName = parser.getValueAsString();
parser.nextToken();
if (!"score".equals(parser.getCurrentName())) {
throw new IllegalStateException(...);
}
parser.nextToken();
JsonDeserializer<Object> scoreDeserializer = context.findNonContextualValueDeserializer(context.constructType(Score.class));
Score score = (Score) scoreDeserializer.deserialize(parser, context);
My server return results having 2 different types: SearchResultDTO when successefull and String when error. I need to handle these 2 types and return always SearchResultDTO type. Here is my deserializer:
public class SearchResultsDeserializer extends JsonDeserializer<SearchResultDTO> {
#Override
public SearchResultDTO deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_STRING){
return new SearchResultDTO(jp.getText());
} else {
return jp.readValueAs(SearchResultDTO.class);
}
}
}
When i run this code and server send SearchResultDTO object, jackson go in infinite loop by calling this function and returns with error: "java.lang.StackOverflowError: stack size 1036KB"
Easy fix would be to create new instance of ObjectMapper in your SearchResultsDeserializer and use it instead of JsonParser
public static class SearchResultsDeserializer extends JsonDeserializer<SearchResultDTO> {
private static ObjectMapper mapper = new ObjectMapper();
#Override
public SearchResultDTO deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_STRING){
return new SearchResultDTO(jp.getText());
} else {
return mapper.readValue(jp, SearchResultDTO.class);
}
}
}
The hard way is to save default deserializer in your custom deserializer.
Here is more info: How do I call the default deserializer from a custom deserializer in Jackson