Parse POST body to java object using spark
Asked Answered
I

2

7

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
Ignaz answered 26/1, 2015 at 20:48 Comment(4)
Are you sure you chose the right tag 'Apache Spark' spark.apache.org ?Acosmism
@Acosmism sorry my bad, now I see that there is no tag for sparkIgnaz
I think that there isn't such a function yet. Try to use request.params("id"); ... at least not in the docsJackfish
@JorgeCampos But params is empty because POST method sends the data in the body. Just checked and the data is only in the body, not in the request.params()Ignaz
I
7

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'}]
Inquiry answered 26/1, 2015 at 21:12 Comment(2)
Nice approach, if spark has nothing built-in then this may be the best solution. ThanksIgnaz
Do you know if there is any way to make this with lists? For example that one key is: example[0].id ?Ignaz
K
3

I found an easier way that doesn't involve URL encoding.

On the client, turn your javascript object to a JSON string and set a query parameter (yourObject) with it:

var obj = null;
obj = {
  yourObject: JSON.stringify(currentObject)
};
   
$.ajax({
  type: "GET",
  url: "saveAnObject",
  data: obj, 
  contentType: "application/json; charset=utf-8",
  dataType: "json",
  success: function(data) {
    console.log('saveAnObject result: ' + data + ".");

  },
  error: function() {
    
  },
  cache: false
});

Then in Spark:

 get("/saveAnObject", (req, res) - > {
     
     String yourObjectStr = "" + req.queryParams("yourObject");
   
     // Convert the JSON string to a POJO obj
     Gson gson = new GsonBuilder().create();
     YourObject pojoObj = gson.fromJson(yourObjectStr , YourObject.class);

     // do something with your pojoObj object.
   
Kliment answered 3/5, 2016 at 3:48 Comment(3)
Why would I do that? If i am going to make an ajax then I would send the json in the body as content type json, but this is not the case. I am talking about forms submitted that send the data serialised that way, not in json format.Ignaz
Using this method, you can pass multiple objects of multiple class types in the one Ajax GET. So you could send an Account object as well as your User object. I find the req.queryParams("yourObject") to be convenient for parsing these objects. I tend not to use form submission, your original question didn't mention form submission.Kliment
Yeah, so this is the common sense solution, but you don't have to wrap the whole object, the body of the request can just be json. So, rather than doing the first 4 lines of code, simply change data: obj, to data: JSON.stringify(currentObject).Cheapen

© 2022 - 2024 — McMap. All rights reserved.