2

I have a long switch statement that I'm trying to make more efficient. It parses an xml feed and populates a Java object using the xml values. There are maybe 30 fields so it's kind of tedious to write a switch case for each field.

switch (currentTagName) {
    case "longitude" :
        observation.setLongitude(Double.parseDouble(parser.getText()));
        break;
    case "elevation" :
        observation.setElevation(Integer.parseInt(parser.getText()));
        break;
    case "observation_time" :
        observation.setObservation_time(parser.getText());
        break;

You can see, the only difference in how each case is handled is due to the type of data I'm working with.

I'm trying to figure out the syntax for doing something similar to this (pseudocode):

//get the data type of this variable, somehow or other
String inputType = Observation.getMethodInputType("set" + currentTagName); 

//switch on that data type
switch(inputType) {
    case "Integer":
        observation.set{currentTagName}(Integer.parseInt(parser.getText()));
        break;
    case "Double": 
        observation.set{currentTagName}(Double.parseDouble(parser.getText()));
        break;
    case "String":
        observation.set{currentTagName}(parser.getText());
        break;
}

It's just that my Java syntax is pretty rusty, I'm not sure what the correct way is to do this (or if you even can), especially for the set{currentTagName} part and getMethodInputType() (is that a thing?).

What's the correct way to do this?

Kluny
  • 189
  • 3
  • 17

5 Answers5

3

Your solution will be pretty unmaintainable. Java has few popular methods of parsing xml in compact way (without tedious string operations).

You should check:

  • JAXB (recommended) which provides good domain encapsulation and very readable code

  • DOM which is semi readable and semi fast

  • SAX which is very fast and least maintanable

For JAXB you will have to create object/objects which will recreate xml structure and conversion will happen automatically for you.

Marcin Szymczak
  • 11,199
  • 5
  • 55
  • 63
  • Wow, I forgot the extent to which Java has a library for everything. I think JAXB is what I'm after. – Kluny Aug 29 '15 at 21:50
1

Don't use a switch. Make an interface for conversion of String to an object by type, populate a Map<Class,Converter> with converters of appropriate type, and use converters from that map to parse your input.

interface Converter {
    Object convert(String s);
}
private static Map<Class,Converter> converterForClass = new HashMap<>();
static {
    converterForClass.put(Integer.TYPE, s -> Integer.parseInt(s));
    converterForClass.put(Double.TYPE, s -> Double.parseDouble(s));
    converterForClass.put(String.class, s -> s);
    converterForClass.put(Long.TYPE, s -> Long.parseLong(s));
};

Now you can convert Strings to Objects of the appropriate type like this:

Object a = converterForClass.get(Integer.TYPE).convert("123");

Here is a demo that uses reflection to access the field:

static class Demo {
    public int a;
    public long b;
    public String c;
    public double d;
    @Override
    public String toString() {
        return a+":"+b+":'"+c+"':"+d;
    }
}

public static void main (String[] args) throws java.lang.Exception
{
    Object a = new Demo();
    Map<String,String> data = new HashMap<>();
    data.put("a", "123");
    data.put("b", "123456678789898");
    data.put("c", "HELLO");
    data.put("d", "123.456");
    for (Field f : Demo.class.getDeclaredFields()) {
        f.setAccessible(true);
        String str = data.get(f.getName());
        Object r = converterForClass.get(f.getType()).convert(str);
        f.set(a, r); // Call set(target, objValue) instead of setInt, setLong, etc.
    }
    System.out.println(a);
}

Running demo.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    Though a nice solutions, the problem with calling different setter methods (with different names) for different tags stays. Perhaps you improve your solution with a bit of fairy dust aka reflection which makes a lookup for the field to put the data into. – Thomas Junk Aug 29 '15 at 19:51
  • @ThomasJunk The problem of having to call different methods for different types goes away completely, because there is a `set` method that takes an `Object` (see demo). Of course if one insists on calling different methods based on the type, instead of converting and returning the object the lambda could take a field and a target as additional parameters, and do the setting internally. – Sergey Kalinichenko Aug 29 '15 at 19:57
1

You can simply convert it to an object and get its class name using:

((Object) yourVar).getClass().getName()

Then you can use it in your switch statement:

String typeName =  ((Object) inputType).getClass().getName();

switch(typeName) { 

   case "Integer":      

            observation.set{currentTagName}(Integer.parseInt(parser.getText())); 
   break; 

   case "Double":

             observation.set{currentTagName}(Double.parseDouble(parser.getText())); 
    break; 

    case "String":

              observation.set{currentTagName}(parser.getText()); 
    break;
 }

Take a look at How to determine the primitive type of a primitive variable? for further information.

Community
  • 1
  • 1
cнŝdk
  • 31,391
  • 7
  • 56
  • 78
  • This solution would work but this may not be a good use case for the switch statements. – Arefe May 05 '20 at 05:49
  • @ChakladerAsfakArefe Yes I completely agree that `switch` case is not the best solution here, but I was just rectifyingthe OP code. – cнŝdk May 05 '20 at 09:50
0

Convert either the int and double into a string, and then use one if statement. Use the following syntax:

Integer.toString(i)
//Makes an integer into a string

and

Double.toString(i)

//Makes a double into a string

Once you have everything as a string, compare strings. This way the variable type is the same, and it makes the code much easier to deal with.

Good luck!

Ruchir Baronia
  • 7,406
  • 5
  • 48
  • 83
0

I've written a little Java-8 library called unXml, that might be of help to you. It's open sourced on Github, and available on Maven Central.

You can create an ObjectParser that will pick out a value from an XPath, and put it into a JsonObject as an attribute. Then it uses Jackson's json-mapping to instantiate your class.

Read up on Jacksons Json to Object mapping, to see how to best fit it into your classes.

Example:

<root>
    <entry id="1">
        <observation>100</observation>
        <longitude>150</longitude>
        <observation-time>15 sec</observation-time>
    </entry>
</root>

Lets say your area of expertise is birdspotting, and this is your class:

@JsonIgnoreProperties(ignoreUnknown = true)
public static class BirdSpotting {
    public Integer id;
    public Integer observation;
    public Double longitude;
    public String observationTime;
}

You can create a (reusable) ObjectParser, and apply it to your xml.

public List<BirdSpotting> getData(Document inputXml) {
    Parsing parsing = ParsingFactory.getInstance().create();

    ObjectParser<List<BirdSpotting>> parser = 
        parsing.arr("/root/entry", parsing.obj()
            .attribute("id", "@id")
            .attribute("observation", "observation", parsing.with(Integer::parseInt))
            .attribute("longitude", "longitude", parsing.with(Double::parseDouble))
            .attribute("observationTime", "observation-time")
    ).as(BirdSpotting.class);

    List<BirdSpotting> spottings = parser.apply(inputXml);
    return spottings;
}

You can also parse into a single Birdspotting object by dropping the parsing.arr()part. Your parser will then be of type ObjectParser<Birdspotting> (without the List).

Output

BirdSpotting [id=1,observation=100,longitude=150.0,observationTime="15 sec"]
tomaj
  • 1,570
  • 1
  • 18
  • 32