14

My basic question: is there anything built that already does this automatically (doesn't have to be part of a popular library/package)? The main things I'm working with are Spring (MVC) and Jackson2.

I understand there are a few manual ways to do this:

  1. Create a method in each class that serializes its specific properties into property=value& form (kind of stinks because it's a bunch of logic duplication, I feel).
  2. Create a function that accepts an object, and uses reflection to dynamically read all the properties (I guess the getters), and build the string by getting each. I'm assuming this is how Jackson works for serialization/deserialization in general, but I really don't know.
  3. Use some feature of Jackson to customly serialize the object. I've researched custom serializers, but it seems they are specific to a class (so I'd have to create one for each Class I'm trying to serialize), while I was hoping for a generic way. I'm just having trouble understanding how to apply one universally to objects. A few of the links:
  4. Use ObjectMapper.convertValue(object, HashMap.class);, iterate over the HashMap's key/value pairs, and build the string (which is what I'm using now, but I feel the conversions are excessive?).
  5. I'm guessing there's others I'm not thinking of.

The main post I've looked into is Java: Getting the properties of a class to construct a string representation

My point is that I have several classes that I want to be able to serialize without having to specify something specific for each. That's why I'm thinking a function using reflection (#2 above) is the only way to handle this (if I have to do it manually).

If it helps, an example of what I mean is with, say, these two classes:

public class C1 {
    private String C1prop1;
    private String C1prop2;
    private String C1prop3;

    // Getters and setters for the 3 properties
}

public class C2 {
    private String C2prop1;
    private String C2prop2;
    private String C2prop3;

    // Getters and setters for the 3 properties
}

(no, the properties names and conventions are not what my actual app is using, it's just an example)

The results of serializing would be C1prop1=value&C1prop2=value&C1prop3=value and C2prop1=value&C2prop2=value&C2prop3=value, but there's only one place that defines how the serialization happens (already defined somewhere, or created manually by me).

So my idea is that I will have to end up using a form of the following (taken from the post I linked above):

public String toString() {
    StringBuilder sb = new StringBuilder();
    try {
        Class c = Class.forName(this.getClass().getName());
        Method m[] = c.getDeclaredMethods();
        Object oo;
        for (int i = 0; i < m.length; i++)
        if (m[i].getName().startsWith("get")) {
            oo = m[i].invoke(this, null);
            sb.append(m[i].getName().substring(3) + ":"
                      + String.valueOf(oo) + "\n");
        }
    } catch (Throwable e) {
        System.err.println(e);
    }
    return sb.toString();
}

And modify it to accept an object, and change the format of the items appended to the StringBuilder. That works for me, I don't need help modifying this now.

So again, my main question is if there's something that already handles this (potentially simple) serialization instead of me having to (quickly) modify the function above, even if I have to specify how to deal with each property and value and how to combine each?

If it helps, the background of this is that I'm using a RestTemplate (Spring) to make a GET request to a different server, and I want to pass a specific object's properties/values in the URL. I understand I can use something like:

restTemplate.getForObject("URL?C1prop1={C1Prop1}&...", String.class, C1Object);

I believe the properties will be automatically mapped. But like I said, I don't want to have to make a different URL template and method for each object type. I'm hoping to have something like the following:

public String getRequest(String url, Object obj) {
    String serializedUri = SERIALIZE_URI(obj);
    String response = restTemplate.getForObject("URL?" + serializedUri, String.class);
    return response;
}

where SERIALIZE_URI is where I'd handle it. And I could call it like getRequest("whatever", C1Object); and getRequest("whateverElse", C2Object);.

Community
  • 1
  • 1
Ian
  • 50,146
  • 13
  • 101
  • 111

2 Answers2

18

I think, solution number 4 is OK. It is simple to understand and clear.

I propose similar solution in which we can use @JsonAnySetter annotation. Please, see below example:

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonProgram {

    public static void main(String[] args) throws Exception {
        C1 c1 = new C1();
        c1.setProp1("a");
        c1.setProp3("c");

        User user = new User();
        user.setName("Tom");
        user.setSurname("Irg");

        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.convertValue(c1, UriFormat.class));
        System.out.println(mapper.convertValue(user, UriFormat.class));
    }
}

class UriFormat {

    private StringBuilder builder = new StringBuilder();

    @JsonAnySetter
    public void addToUri(String name, Object property) {
        if (builder.length() > 0) {
            builder.append("&");
        }
        builder.append(name).append("=").append(property);
    }

    @Override
    public String toString() {
        return builder.toString();
    }
}

Above program prints:

prop1=a&prop2=null&prop3=c
name=Tom&surname=Irg

And your getRequest method could look like this:

public String getRequest(String url, Object obj) {
    String serializedUri = mapper.convertValue(obj, UriFormat.class).toString();
    String response = restTemplate.getForObject(url + "?" + serializedUri, String.class);
    return response;
}
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • 3
    **VERY** interesting and cool. I will definitely go with this, unless I hear other suggestions. I agree #4 was pretty good - I'm guessing it does #2 behind the scenes anyways, so I might as well keep it (except I really like this solution, so I will use it :) ). – Ian Aug 23 '13 at 20:28
  • I glad to hear it. Feel free to use it. Jackson is reliable and fast so I think, you should reuse as much source code as you can from Jackson library. You should also remember about URL encoding and other stuff like that. – Michał Ziober Aug 23 '13 at 20:48
  • Cool, I appreciate it. This answer is exactly what I was looking for - a feature of Jackson that I didn't know about (because I don't know nearly enough about, or utilize, the library). I don't know if it's applicable, but it seemed like using `@JsonView` might be another alternative. Do you have any insight on how to use it in this scenario or why (not) to use it in comparison to your solution? – Ian Aug 24 '13 at 18:35
  • 1
    @JsonView defines class/interface which contains only this properties which we want to serialize to JSON. I do not know how to use this annotation to solve this problem. – Michał Ziober Aug 24 '13 at 19:13
3

Lets we have c1.

c1.setC1prop1("C1prop1");
c1.setC1prop2("C1prop2");
c1.setC1prop3("C1prop3");

Converts c1 into URI

    UriComponentsBuilder.fromHttpUrl("http://test.com")
            .queryParams(new ObjectMapper().convertValue(c1, LinkedMultiValueMap.class))
            .build()
            .toUri());

After we will have

    http://test.com?c1prop1=C1prop1&c1prop2=C1prop2&c1prop3=C1prop3