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?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
veer7
  • 20,074
  • 9
  • 46
  • 74

1 Answers1

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)
mvmn
  • 3,717
  • 27
  • 30
  • 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. – mvmn Sep 28 '21 at 18:49