0

I have a JSON created by Elixir class which has a field which can be string or array:

field :logs, {:array, :string}

If anyone doesn't get this it will be like

{"logs" : "some log 1"}

or

{
"logs": ["some log 1", "some log 2"]
}

I have a Java field mapped for this:

@JsonProperty("logs")
private String logs;

This mapping works only when the logs comes as a String, but fails if the logs comes as array with error saying it will not be able to convert START_ARRAY to string.

How to serialize the field if it comes as array and store it as a comma separated string?

1
  • Use Object and then check the actual type with instanceof? Commented Sep 28, 2021 at 17:00

1 Answer 1

0

I see in tags that you use Jackson for parsing. This means you need to write and register with Jackson a custom deserializer for your logs field.

An example of such solution:

package tmp;

import java.io.IOException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ValueNode;

public class JacksonDemo {

    public static class LogHolder {
        @JsonProperty("logs")
        @JsonDeserialize(using = ArrayOrStringJsonDeserializer.class)
        private String logs;

        @Override
        public String toString() {
            return "LogHolder(logs=" + logs + ")";
        }
    }

    public static class ArrayOrStringJsonDeserializer extends JsonDeserializer<String> {

        @Override
        public String deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            JsonNode node = (JsonNode) jsonParser.readValueAsTree();
            if (node.isValueNode()) {
                ValueNode valueNode = (ValueNode) node;
                if (valueNode.isTextual()) {
                    return valueNode.textValue();
                }
            } else if (node.isArray()) {
                ArrayNode arrayNode = (ArrayNode) node;
                return StreamSupport.stream(Spliterators.spliteratorUnknownSize(arrayNode.iterator(), Spliterator.ORDERED), false)
                        .map(JsonNode::textValue)
                        .collect(Collectors.joining(", "));
            }
            throw MismatchedInputException.from(jsonParser, String.class,
                    "Expected node to be of type String or array, but got " + node.getNodeType().toString());
        }

    }

    public static void main(String args[]) throws Exception {
        String[] docs = { "{\"logs\" : \"some log 1\"}", "{\"logs\": [\"some log 1\", \"some log 2\"]}" };

        ObjectMapper om = new ObjectMapper();
        for (String doc : docs) {
            System.out.println(om.readValue(doc, LogHolder.class));
        }
    }
}

Result of executing this code:

LogHolder(logs=some log 1)
LogHolder(logs=some log 1, some log 2)
Sign up to request clarification or add additional context in comments.

1 Comment

P.S. Note I've been a bit strict here with type checking - explicitly allowing only String and Array JSON types, and only taking textual values from the array (null will be returned for all other types). But - if needed - it's easy to make this more "relaxed" and also allow numbers etc.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.