Pablo Matias Gomez Pablo Matias Gomez - 7 months ago 43
Java Question

Parse POST body to java object using spark

I migrated from spring to spark while ago and now I'm stuck at something basic.

When I make a POST request sending data in the body I want to have the JAVA object back in the controller..

In spring I used to do

@RequestBody User user


And it was "filled" automatically..

Now with spark I have the method:

request.body();


But that gives me a serialized string like this:

id=7&name=Pablo+Mat%C3%ADas&lastname=Gomez&githubUsername=pablomatiasgomez


So how can I get the User DTO ?

Of course, the User class has the properties


  • id

  • name

  • lastname

  • githubUsername


Answer

AFAIK, Spark does not offer this functionality. When I used it for a small pet-project, I wrote some small utility methods to parse the URL encoded string into a POJO like this:

import com.google.gson.Gson;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Map;

public class Test {

  private static final Gson GSON = new Gson();

  public static <T> T convert(String urlencoded, Class<T> type) {
    try {
      Map<String, Object> map = asMap(urlencoded);
      String json = GSON.toJson(map);
      return GSON.fromJson(json, type);
    }
    catch (Exception e) {
      e.printStackTrace(); // TODO log
      return null;
    }
  }

  public static Map<String, Object> asMap(String urlencoded) throws UnsupportedEncodingException {
    return asMap(urlencoded, "UTF-8");
  }

  @SuppressWarnings("unchecked")
  public static Map<String, Object> asMap(String urlencoded, String encoding) throws UnsupportedEncodingException {

    Map<String, Object> map = new LinkedHashMap<>();

    for (String keyValue : urlencoded.trim().split("&")) {

      String[] tokens = keyValue.trim().split("=");
      String key = tokens[0];
      String value = tokens.length == 1 ? null : URLDecoder.decode(tokens[1], encoding);

      String[] keys = key.split("\\.");
      Map<String, Object> pointer = map;

      for (int i = 0; i < keys.length - 1; i++) {

        String currentKey = keys[i];
        Map<String, Object> nested = (Map<String, Object>) pointer.get(keys[i]);

        if (nested == null) {
          nested = new LinkedHashMap<>();
        }

        pointer.put(currentKey, nested);
        pointer = nested;
      }

      pointer.put(keys[keys.length - 1], value);
    }

    return map;
  }

  public static void main(String[] args) {
    String payload = "id=7&name=Pablo+Mat%C3%ADas&lastname=Gomez&githubUsername=pablomatiasgomez";
    User user = convert(payload, User.class);
    System.out.println(user);
  }
}

class User {

  long id;
  String name;
  String lastname;
  String githubUsername;

  @Override
  public String toString() {
    return "User{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", lastname='" + lastname + '\'' +
        ", githubUsername='" + githubUsername + '\'' +
        '}';
  }
}

Running this Test class will print the following on your console:

User{id=7, name='Pablo Matías', lastname='Gomez', githubUsername='pablomatiasgomez'}

Note that this also work when a User has a nested structure in it, say, an Address which is composed of several other fields. jus separate the fields with "."'s like this:

public class Test {

  // ... same code ...

  public static void main(String[] args) {
    String payload = "id=7&name=Pablo+Mat%C3%ADas&lastname=Gomez&githubUsername=pablomatiasgomez&" +
        "address.street=Coolsingel&address.number=42a&address.city=Rotterdam";
    User user = convert(payload, User.class);
    System.out.println(user);
  }
}

class User {

  long id;
  String name;
  String lastname;
  String githubUsername;
  Address address;

  @Override
  public String toString() {
    return "User{" +
        "\n  id=" + id +
        "\n  name='" + name + '\'' +
        "\n  lastname='" + lastname + '\'' +
        "\n  githubUsername='" + githubUsername + "'" +
        "\n  address=" + address + "\n" +
        '}';
  }
}

class Address {


  String street;
  String number;
  String city;

  @Override
  public String toString() {
    return "Address{" +
        "street='" + street + '\'' +
        ", number='" + number + '\'' +
        ", city='" + city + '\'' +
        '}';
  }
}

which will print:

User{
  id=7
  name='Pablo Matías'
  lastname='Gomez'
  githubUsername='pablomatiasgomez'
  address=Address{street='Coolsingel', number='42a', city='Rotterdam'}
}

EDIT

And if the payload contains a list of, say Users, you could do something like this:

public class Test {

  private static final Gson GSON = new Gson();

  public static <T> T convert(String urlencoded, Type type) {
    try {
      Map<String, Object> map = asMap(urlencoded);
      String json = GSON.toJson(containsList(map) ? map.values() : map);
      return GSON.fromJson(json, type);
    }
    catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  private static boolean containsList(Map<String, Object> map) {
    return !map.isEmpty() && new ArrayList<>(map.keySet()).get(0).contains("[");
  }

  public static Map<String, Object> asMap(String urlencoded) throws UnsupportedEncodingException {
    return asMap(urlencoded, "UTF-8");
  }

  @SuppressWarnings("unchecked")
  public static Map<String, Object> asMap(String urlencoded, String encoding) throws UnsupportedEncodingException {

    Map<String, Object> map = new LinkedHashMap<>();

    for (String keyValue : urlencoded.trim().split("&")) {

      String[] tokens = keyValue.trim().split("=");
      String key = tokens[0];
      String value = tokens.length == 1 ? null : URLDecoder.decode(tokens[1], encoding);

      String[] keys = key.split("\\.");
      Map<String, Object> pointer = map;

      for (int i = 0; i < keys.length - 1; i++) {

        String currentKey = keys[i];
        Map<String, Object> nested = (Map<String, Object>) pointer.get(keys[i]);

        if (nested == null) {
          nested = new LinkedHashMap<>();
        }

        pointer.put(currentKey, nested);
        pointer = nested;
      }

      pointer.put(keys[keys.length - 1], value);
    }

    return map;
  }

  public static void main(String[] args) throws Exception {

    String payload = "id=7&name=Pablo Mat%C3%ADas";
    User user = convert(payload, User.class);
    System.out.println("single user   -> " + user);

    payload = "users[0].id=7&users[0].name=Pablo Mat%C3%ADas&users[1].id=42&users[1].name=Bart";
    List<User> users = convert(payload, new TypeToken<List<User>>(){}.getType());
    System.out.println("list of users -> : " + users);
  }
}

class User {

  long id;
  String name;

  @Override
  public String toString() {
    return "User{" +
        "id=" + id +
        ", name='" + name + '\'' +
        '}';
  }
}

which will print:

single user   -> User{id=7, name='Pablo Matías'}
list of users -> : [User{id=7, name='Pablo Matías'}, User{id=42, name='Bart'}]