4

In my application I have lot of overviews (tables) with sorting and filtering capabilities. And becuase the different column can hold different value type (strings, numbers, dates, sets, etc.) the filter for these columns also can bring different values. Let me show you few examples (converted to JSON already as is sent to server via REST request):

For simple string value it is like:

{"<column_name>":"<value>"}

For number and date column the filter looks like:

{"<column_name>":[{"operator":"eq","value":"<value>"}]}
{"<column_name>":[{"operator":"eq","value":"<value1>"},{"operator":"gt","value":"<value2>"}]}

For set the filter looks like

{"<column_name>":["<value1>","<value2>"(,...)]}

Now I need to parse that JSON within a helper class that will build the WHERE clause of SQL query. In PHP this is not a problem as I can call json_decode and then simply check whether some value is array, string or whatever else... But how to do this simply in Java?

So far I am using Spring's JsonJsonParser (I didn't find any visible difference between different parsers coming with Spring like Jackson, Gson and others).

I was thinking about creating an own data object class with three different constructors or having three data object classes for all of the three possibilities, but yet I have no clue how to deal with the value returned for column_name after the JSON is parsed by parser...

Simply looking on the examples it gives me three possibilities:

  1. Map<String, String>
  2. Map<String, Map<String, String>>
  3. Map<String, String[]>

Any idea or clue?

2
  • Can you clarify what JsonJsonParser is? Commented Sep 22, 2015 at 16:03
  • @SotiriosDelimanolis It's part of org.springframework.boot.json package, consisting of these parsers: Basic, Gson, Jackson, Json, JsonSimple and Yaml (suffix them with JsonParser to get the full name so you'll get e.g. JsonJsonParser). Commented Sep 23, 2015 at 11:51

2 Answers 2

3

Jackson's ObjectMapper treeToValue should be able to help you.

http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/ObjectMapper.html#treeToValue%28com.fasterxml.jackson.core.TreeNode,%20java.lang.Class%29

Your main problem is that the first version of you JSON is not the same construction than the two others. Picking the two others you could deserialize your JSON into a Map<String, Map<String, String> as you said but the first version fits a Map.

There are a couple solutions available to you :

  • You change the JSON format to always match the Map<String, Map<String, String> pattern
  • You first parse the JSON into a JsonNode, check the type of the value and deserialize the whole thing into the proper Map pattern.
  • (quick and dirty) You don't change the JSON, but you try with one of the Map patterns, catch JsonProcessingException, then retry with the other Map pattern
Sign up to request clarification or add additional context in comments.

1 Comment

I suggest drop the Map and just work with JsonNode and its subtypes.
2

You'll have to check the type of the values in runtime. You can work with a Map<String, Object> or with JsonNode.

Map<String, Object>

JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> map = parser.parseMap(str);
Object filterValue = filter.get("<column_name>");
if (filterValue instanceof String) {
  // str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue instanceof Collection) {
  for (Object arrayValue : (Collection<Object>) filterValue) {
    if (arrayValue instanceof String) {
      // str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
    } else if (arrayValue instanceof Map) {
      // str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
    }
  }
}

JsonNode

ObjectMapper mapper = new ObjectMapper();
JsonNode filter = mapper.readTree(str);
JsonNode filterValue = filter.get("<column_name>");
if (filterValue.isTextual()) {
  // str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue.isArray()) {
  for (JsonNode arrayValue : filterValue.elements()) {
    if (arrayValue.isTextual()) {
      // str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
    } else if (arrayValue.isObject()) {
      // str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
    }
  }
}

2 Comments

Thanks, I like your answer as it brings code samples. Looking at them I can see there is no really big difference in between using Map<String, Object> or JsonNode, is it? I think I'll go with JsonNode as it seems to me nicer...
Yeah, I'd prefer JsonNode, as with Map<String, Object> you get a higher level representation of the JSON data...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.