1

I have the JSON looks like the following:

{
  "name": "john",
  "options": {
    "test": 1,
    "operation": "op1",     // I need to deserialize this option to enum.
    ...
    // any number of options
  }
}

and I have the class looks like the following:

public class Info {
    public String name;
    @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
    public Map<String, Object> options;
}

public enum OperationEnum {OP1, OP2, OP3}

How can I deserialize to options map operation like enum and test to Integer

John
  • 1,375
  • 4
  • 17
  • 40
  • 1
    Why use a map and not DTO class ? – Maelig Nov 23 '20 at 14:59
  • The ```Info``` class is DTO. I'm using a map inside because I can get any number of options. – John Nov 23 '20 at 15:11
  • You could use a custom deserializer then for your `Info` class and check data in options map to see if there are `OperationEnum`. For each of them you can then use `OperationEnum.valueOf` https://stackoverflow.com/questions/19158345/custom-json-deserialization-with-jackson – Maelig Nov 23 '20 at 15:57

4 Answers4

1

You should be able to use @JsonAnySetter to help you out here:

public class Info {
    public static class Options {
      public int test;
      public OperationEnum operation;
    }

    public String name;
    public Options options;
    private final Map<String, Object> properties = new HashMap<>();

    @JsonAnySetter
    public void add(String key, String value) {
        properties.put(key, value);
    }

}

public enum OperationEnum {OP1, OP2, OP3}

There's a good write-up here: https://www.concretepage.com/jackson-api/jackson-jsonanygetter-and-jsonanysetter-example

Matthew
  • 10,361
  • 5
  • 42
  • 54
0

Why not do it like this:


public class Info {
    public static class Options {
      public int test;
      public OperationEnum operation;
    }

    public String name;
    public Options options;
}

public enum OperationEnum {OP1, OP2, OP3}

Then it should JustWork!

Matthew
  • 10,361
  • 5
  • 42
  • 54
  • I can't do this because I need a map of options. I can get any number of parameters and I need to process it. – John Nov 23 '20 at 15:13
  • I see. I'm not sure there's a great solution - see also here: https://stackoverflow.com/questions/58102069/how-to-do-a-partial-deserialization-with-jackson – Matthew Nov 23 '20 at 15:56
0

JSON structure, where all options (of different kinds) are stored like Map<String, Object> options; enforces extra parsing, because value type is identified based on the label (like "operation" and OperationEnum class) Here are 2 approaches with ObjectMapper.readValue and Enum.valueOf

If it's possible, consider more flexible solutions like those from @Matthew

class Demo {
    public static void main(String[] args) throws Exception {
        ObjectMapper om = new ObjectMapper();
        Info info = om.readValue(jsonValue, Info.class);
        OperationEnum operationM = om.readValue("\"" + info.options.get("operation") + "\"", OperationEnum.class);
        System.out.println(operationM);

        OperationEnum operationE = OperationEnum.valueOf(info.options.get("operation").toString());
        System.out.println(operationE);
    }

    static String jsonValue = "{\n" +
            "  \"name\": \"john\",\n" +
            "  \"options\": {\n" +
            "    \"test\": 1,\n" +
            "    \"operation\": \"OP1\" \n" +
            "  }\n" +
            "}";
}
Elzzz
  • 131
  • 5
  • But after ```readValue``` I want to do something like this: ```(OperationEnum)info.options.get("operation")``` – John Nov 23 '20 at 15:18
  • I'm note sure it'll work, did you try it ? Because how Jackson will bind OP1 to the enum `OperationEnum` ? – Maelig Nov 23 '20 at 15:23
  • @Maelig: yes it works and requires some extra parsing – Elzzz Nov 23 '20 at 19:01
0

Simple solution is to wrap Map instance with a class and implement extra methods for each non-regular property. You can use @JsonAnySetter annotation to store all key-value pairs.

See below example:

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

public class JsonOptionsWithCustomEnumApp {
    public static void main(String[] args) throws IOException {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = new ObjectMapper();
        Info info = mapper.readValue(jsonFile, Info.class);
        Options options = info.getOptions();
        System.out.println(options.toMap());
        System.out.println(options.getOperation());
    }
}

class Info {
    private String name;
    private Options options;

    //getters, setters
}

class Options {
    private Map<String, Object> values = new LinkedHashMap<>();

    @JsonAnySetter
    private void setValues(String key, Object value) {
        values.put(key, value);
    }

    public OperationEnum getOperation() {
        Object operation = values.get("operation");
        if (operation == null) {
            return null;
        }
        return Arrays.stream(OperationEnum.values())
            .filter(op -> op.name().equalsIgnoreCase(operation.toString()))
            .findFirst()
            .orElse(null);
    }

    public Map<String, Object> toMap() {
        return values;
    }
}

enum OperationEnum {OP1, OP2, OP3}

Above code prints:

{test=1, operation=op1}
OP1
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146