Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize ANY Object into a URI?

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:
    • http://techtraits.com/Programming/2011/11/20/using-custom-serializers-with-jackson/
    • http://wiki.fasterxml.com/JacksonHowToCustomSerializers
  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);.

like image 690
Ian Avatar asked Aug 23 '13 17:08

Ian


2 Answers

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;
}
like image 93
Michał Ziober Avatar answered Oct 16 '22 15:10

Michał Ziober


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
like image 32
Maksim Pristsepov Avatar answered Oct 16 '22 16:10

Maksim Pristsepov