Dynamically determining user input type in JAVA

I wrote the following code to determine the type of data input the user entered.

Update . Deleted parsing in Float , since the Double value can also be parsed in Float for the price of some accuracy, as indicated in @DodgyCodeException

 import java.util.Scanner; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class Main { public static void main(String[] args) { Scanner src = new Scanner(System.in); String input; System.out.println("Enter input:"); input = src.nextLine(); System.out.println("You entered:" + getDataType(input)); } private static String getDataType(Object input) { try { JSONObject obj = new JSONObject((String) input); return "JSONObject"; } catch (JSONException ex) { try { JSONArray array = new JSONArray(input); return "JSONArray"; } catch (JSONException ex0) { try { Integer inti = Integer.parseInt((String) input); return "Integer"; } catch (NumberFormatException ex1) { try { Double dub = Double.parseDouble((String) input); return "Double"; } catch (NumberFormatException ex3) { return "String"; } } } } } } } 

I need to run this several times hundreds of times, and I read that catching an Exception is an expensive operation.
Is there a better way to achieve this?

+8
java json
source share
4 answers

My approach would be to let the framework do its job and use it to parse input:

 Object obj = new org.json.JSONTokener(input).nextValue(); if (obj instanceof JSONArray) return "JSONArray"; if (obj instanceof JSONObject) return "JSONObject"; if (obj instanceof Integer) return "Integer" ... 

Perhaps use a switch or Map switch instead of a long list of if constructs. And catch a JSONException for input that is not valid JSON.

It might be simplified to return obj.getClass() or obj.getClass().getSimpleName() for the almost identical functionality of what you have.

+7
source share

Honestly, I don't like the idea of ​​type definition using parser exceptions. I would build a template for each format and list all of them to check if a given line matches one of them.

This is similar to how the Scanner class works, but it is rather complicated. It is difficult to write regular expressions that should cover all possible cases.

But if regular expressions are prepared, the algorithm is simple:

 public class Main { private final Map<String, String> patterns = Map.of( "Integer", integerPattern() ); public String getDataType(String input) { for (Map.Entry<String, String> entry : patterns.entrySet()) { if (Pattern.matches(entry.getValue(), input)) { return entry.getKey(); } } return "Unknown"; } } 

Here's the interesting part - integerPattern() example. I took it from Scanner and simplified it a bit:

 private String integerPattern() { String radixDigits = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, 10); String digit = "((?i)[" + radixDigits + "]|\\p{javaDigit})"; String groupedNumeral = "(" + "[\\p{javaDigit}&&[^0]]" + digit + "?" + digit + "?(" + "\\," + digit + digit + digit + ")+)"; String numeral = "((" + digit + "++)|" + groupedNumeral + ")"; String javaStyleInteger = "([-+]?(" + numeral + "))"; String negativeInteger = "\\-" + numeral + ""; String positiveInteger = "" + numeral + ""; return "(" + javaStyleInteger + ")|(" + positiveInteger + ")|(" + negativeInteger + ")"; } 

I am sure you can google all regular expressions or find them here.

But what if there is an β€œeasier” solution or you just don't want to use a regex approach?

You can mix different methods by entering Map<String, Predicate<String>> :

 class Main { private final Map<String, Predicate<String>> predicates = Map.of( "Integer", this::isInteger, "JSON", this::isJSON ); public String getDataType(String input) { for (Map.Entry<String, Predicate<String>> predicate : predicates.entrySet()) { if (predicate.getValue().test(input)) { return predicate.getKey(); } } return "Unknown"; } private boolean isJSON(String input) { return LibraryClass.isJSON(input); } private boolean isInteger(String input) { try { Integer.valueOf(input); } catch (NumberFormatException e) { return false; } return true; } } 

I believe that it is more readable, it reduces the level of nesting and encapsulates various logic in separate methods.

+5
source share

I want to know if the input is json / jsonarray or not.

You need to handle a JSONException to determine the type of JSON, and then you can use the NumberFormat parse () method to determine another type, as shown below:

  private static String getDataType(String input) { try { return getJsonObjectType(input); } catch (JSONException ex1) { try { return getJsonArrayType(input); } catch (JSONException ex2) { return getNonJsonType(input); } } } private static String getJsonArrayType(String input) throws JSONException { new JSONArray(input); return "JSONArray"; } private static String getJsonObjectType(String input) throws JSONException { new JSONObject((String) input); return "JSONObject"; } private static String getNonJsonType(String input) { try { return getNumberType(input); } catch(ParseException ex3) { return "String"; } } private static String getNumberType(String input) throws ParseException { Number number = NumberFormat.getInstance().parse(input); if(number instanceof Long) { if(number.longValue() < Integer.MAX_VALUE) { return "int"; } else { return "long"; } } else { if(number.doubleValue() < Float.MAX_VALUE) { return "float"; } else { return "double"; } } } 
0
source share

Long and short:

  • There is no reasonable way to encapsulate this exact functionality without catching these exceptions.
  • If you talk about running this method hundreds of times, it really shouldn't be a performance bottleneck. If so, then compare it, find out where the bottlenecks are in this method, and then refer to them specifically.

To complete, however, there are similar things that you can accomplish without catching these exceptions - although please keep in mind that I have not done the tests, and these results may or may not be faster in your case!

For development, if something is JSON, the mayBeJSON() method mayBeJSON() in JSON-lib , but it is a rather outdated library (last update 2010).

If you want to know if something will fit into an int specifically, then there is no other sensible way than actually disabling NFE with parseInt() . However, if you just want to find out if something is a number, you can simply use the regex:

 str.matches("\\d+"); //Returns true if it a whole positive number str.matches("-?\\d+"); //Returns true if it a whole number 

(Other regular expressions are easily calculated / found online for decimal numbers, numbers with exponents, etc.)

Or you can pass it and check each character separately:

 str.chars().allMatch(c->Character.isDigit(c)); 

Alternatively, you can prefer or replace your exception checks with NumberUtils.isCreateable () from Apache Commons ( isParseable() and other methods in this class can also be useful.)

0
source share

All Articles