-2

I'm attempting to refactor my deserialization method to use generics to enable me to deserialize any type. I can do this just fine for objects that are not in a collection, like this:

public static <T> T parseProductData(String jsonData, Class<T> typeClass) throws IOException, IllegalAccessException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    T inputMessage = objectMapper.readValue(jsonData, typeClass);
    return inputMessage;
}

Here is the method that I want to refactor:

public static List<ComponentPOCO> parseJsonComponentFromString(String fileContents){

        try {
            ObjectMapper mapper = new ObjectMapper()
                    .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            List<ComponentPOCO> component = mapper.readValue(fileContents, new TypeReference<List<ComponentPOCO>>() {});
            return component;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

Here is my failed attempt to refactor the method to use generics:

public static List<T> T parseJsonComponentFromString(String fileContents, Class<T> typeClass){

        try {
            ObjectMapper mapper = new ObjectMapper()
                    .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            List<T> component = mapper.readValue(fileContents, new TypeReference<List<T>>() {});
            return component;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

However, this code doesn't compile because it's not using Java generics correctly. How could I deserialize my JSON objects into a generic list/collection/something-similar type?

Here is an example of the data that I'm deserializing into the ComponentPOCO class:

[
      {   "artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/sample/sample.jar.zip",
        "namespace": "exampleNamespace1",
        "name": "exampleName1",
        "tenant": "exampleTenant1"
      },

      {   
        "artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/sample-calculator/sample-calculator-bundle-2.0.jar.zip",
        "namespace": "exampleNamespace1",
        "name": "exampleName2",
        "tenant": "exampleTenant1"
      },
      {   
        "artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/helloworld/helloworld.jar.zip",
        "namespace": "exampleNamespace1",
        "name": "exampleName3",
        "tenant": "exampleTenant1"
      },
      {   
        "artifactPathOrUrl": "http://www.java2s.com/Code/JarDownload/fabric-activemq/fabric-activemq-demo-7.0.2.fuse-097.jar.zip",
        "namespace": "exampleNamespace1",
        "name": "exampleName4",
        "tenant": "exampleTenant1"
      }
]

Here is the code of the ComponentPOCO type:

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.experimental.Accessors;
import org.apache.pulsar.common.io.SinkConfig;
import org.apache.pulsar.common.io.SourceConfig;

import java.util.List;
import java.util.Map;

@Setter
@Getter
@EqualsAndHashCode
@ToString
@Accessors(chain = true)
@Data
public class ComponentPOCO {
    @JsonProperty
    private String namespace;
    @JsonProperty
    private String tenant;
    @JsonProperty
    private String name;
    @JsonProperty
    private String type;
    @JsonProperty
    private String destinationTopicName;
    @JsonProperty
    private String artifactPathOrUrl;
    @JsonProperty
    private String className;
    @JsonProperty
    private List<String> inputs;
    @JsonProperty
    private String output;
    @JsonProperty
    private Map<String, Object> userConfig;
    @JsonProperty
    private String logTopic;
    @JsonProperty
    private Map<String, Object> configs;
    @JsonProperty
    private Integer parallelism;
    @JsonProperty
    public String sinkType;
    @JsonProperty
    private String sourceType;
    @JsonProperty
    public String runtimeFlags;
}
devinbost
  • 4,658
  • 2
  • 44
  • 57
  • 1
    It is my personal but well considered opinion that including a rant against Java in a Java question is a bad idea. Same with calling Java generics implementation "not as intelligent" in a question that shows that you don't know much about the topic. – fdreger Sep 05 '19 at 14:00
  • @fdreger The problem is actually well documented. See, for example, here: https://softwareengineering.stackexchange.com/questions/22642/what-is-wrong-with-javas-generics – devinbost Sep 05 '19 at 14:51
  • 1
    You should build type in dynamic way: `mapper.getTypeFactory().constructCollectionType(List.class, typeClass)`. See: [Using Jackson to deserialize into a Map](https://stackoverflow.com/questions/20105723/using-jackson-to-deserialize-into-a-map/20109694#20109694) and [How to parse nested arrays with Jackson?](https://stackoverflow.com/questions/24273835/how-to-parse-nested-arrays-with-jackson/24274463#24274463) – Michał Ziober Sep 05 '19 at 15:23
  • Also, take a look on example [How do I parametrize response parsing in Java?](https://stackoverflow.com/questions/57581859/how-do-i-parametrize-response-parsing-in-java/57582505#57582505) and [Deserializing or serializing any type of object using Jackson ObjectMapper and handling exceptions](https://stackoverflow.com/questions/56299558/deserializing-or-serializing-any-type-of-object-using-jackson-objectmapper-and-h) – Michał Ziober Sep 05 '19 at 15:25

1 Answers1

0

There are several problems with your code. The quickiest way to make this code work as I think you want is to replace the method with this one:

public LinkedHashMap<String,T> parseJsonComponentFromString(String fileContents){
        try {
            ObjectMapper mapper = new ObjectMapper()
                    .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            return mapper.readValue(fileContents, new TypeReference<T>() {});
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

because ObjectMapper.readValue returns a LinkedHashMap<String,T> (I have no idea why it says just T in the javadocs)

Then you look up your parsed string with get(key_name) if you know the keys or forEach if you don't and don't want to assume them. This last one is better but would require you to use java 8+ and to know about lambda expressions and functional interfaces. Here a tutorial. Here another one.

By the way, I take chance again to point out there is much wrong with this code, you may want to re-design your whole system. If you really really really must use a List, than you have a different problem: "How to convert a LinkedHashMap to a List?". Guess the quickiest way whould be implement the forEach mentioned earlier and push each element on the List.

That is, something like this:

LinkedHashMap<String,Integer> linkedHashMap = XXX.parseJsonComponentFromString(yyy);
LinkedList<T> list = new LinkedList<T>();     
linkedHashMap.forEach((k,i) -> list.add(i));

Hope I helped.

Sterconium
  • 559
  • 4
  • 20
  • What do you mean by "you may want to re-design your whole system"? My goal was to simply deserialize a collection/list/whatever of JSON objects so I could process each one. I'm not sure that I understand the design flaw there. – devinbost Sep 05 '19 at 14:56
  • For example there is no point to use a List: just use the LinkedHasMap as I showed. I just assumed stuff like that happened elsewhere in your project – Sterconium Sep 05 '19 at 15:00
  • What does the string in the LinkedHashMap represent? – devinbost Sep 05 '19 at 15:02
  • It's the "name of the variable" on the [JSON](https://www.json.org/). For instance, take the json: `{ "a":100, "b":"bruno", "c":true }` The String represents the words "a", "b", or "c". – Sterconium Sep 05 '19 at 15:07
  • That violates my use case. My variables are not named. Please see the data in my edit. – devinbost Sep 05 '19 at 15:10
  • It's because that is an array of Objects inside the json. It's something really different from the original question... – Sterconium Sep 05 '19 at 15:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/199024/discussion-between-devinbost-and-sterconium). – devinbost Sep 05 '19 at 15:15