Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine user input data-type dynamically in JAVA

Tags:

java

json

I've written the following code to determine the data-type of input entered by the user.

Update: Removed parsing into Float since a Double value can also be parsed into Float at cost of some precision as mentioned by @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've to run this repeatedly hundreds of time and I've read catching Exception is an expensive operation.
Is there any better way to achieve this?

like image 340
Kainix Avatar asked Feb 08 '18 10:02

Kainix


People also ask

What is dynamic input in Java?

The input given to the java application at the time of execution(runtime) is called Dynamic input.

How do you take dynamic input?

Right-click on the dynamic input button in the status bar and choose "Dynamic Input Settings." Check or uncheck display settings as desired.

How do you give a value dynamically in Java?

There are no dynamic variables in Java. Java variables have to be declared in the source code1. Depending on what you are trying to achieve, you should use an array, a List or a Map ; e.g. It is possible to use reflection to dynamically refer to variables that have been declared in the source code.

How do I know what data type to use in Java?

Sometimes, we need to check the data type of a variable to compute data because we can perform the logical operation with the same type of variables. In order to check the data type, we use getClass() and getSimpleName() method to get class and its name respectively.


4 Answers

I want to know whether input is a json/jsonarray or not.

You need to handle the JSONException to determine the JSON type and then you can use NumberFormat parse() method to determine the other types 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";
             }
         }
    }
like image 56
developer Avatar answered Sep 30 '22 17:09

developer


The long and short of it is:

  • There's no sensible way to encapsulate this exact functionality while not catching those exceptions.
  • If you're talking about running that method "hundreds" of times, it really shouldn't be a performance bottleneck. If it is, then benchmark it, work out where the bottlenecks are in that method, and then address those specifically.

For completion though, there's similar things you can accomplish without catching those exceptions - though please bear in mind I've done no benchmarks, and these results may or may not be faster in your case!

For working out if something's JSON, there's a mayBeJSON() method in JSON-lib, but it's a rather outdated library (last updated 2010.)

If you want to know whether something will fit in an int specifically, then there's no other sensible way than catching the NFE off parseInt() really. However, if you just want to know whether something is a number, you could just use a regex:

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

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

Or you could stream it, and check each character individually:

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

Alternatively, you may wish to prepend or replace your exception checks with NumberUtils.isCreateable() from Apache Commons (isParseable(), and other methods on that class may also be of use.)

like image 22
Michael Berry Avatar answered Oct 11 '22 08:10

Michael Berry


My approach would be to let the framework do its thing, and use it to parse the input a generic way:

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 statement or a Map instead of a long list of if constructs. And catch JSONException for input that is not valid JSON.

Could perhaps be simplified to return obj.getClass() or obj.getClass().getSimpleName() for almost identical functionality to what you have now.

like image 7
Henrik Aasted Sørensen Avatar answered Oct 11 '22 07:10

Henrik Aasted Sørensen


Honestly, I don't like the idea of determining the type by parser's exceptions. I would build a pattern for each format and iterate over all of them to check if the given string matches one of them.

It's similar how the Scanner class works, but it's quite complicated. It's difficult to write the regular expressions which should cover all the possible cases.

But if the regexes are prepared, the algorithm is straightforward:

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 is an interesting part - an example of integerPattern(). I have taken 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 pretty sure you can google all the regexes or find them in here.

But what if there is a "lighter" solution or you simply don't want to use a regex approach?

You can mix up different techniques by introducing a 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 it's more readable, it decreases the level of nesting and encapsulates different logic in separate methods.

like image 5
Andrew Tobilko Avatar answered Oct 11 '22 07:10

Andrew Tobilko