0

All of the web articles I've found about JSON seem to have to do with the specification of the data; I'm looking for tips on a lucid way to implement.

I have a persistent object that I get from Hibernate and a web service that is supposed to return a list of them.

The resulting JSON is supposed to include HATEOAS links mixed in with the data. And the JSON library should not fire any faults (resolve any proxies) but make them into href attributes.

So, if I have 2 instances of Customer and a web service named /customers, I should get JSON that looks like this:

{
    "href" : "/customers",
    "data" : [  {   "id" : 234,
                    "name" : "Bill Shatner",
                    "href" : "/customers/234",
                    "orders" : "/customers/234/orders",
                    "orders-count" : 2
                },
                {
                    "id" : 210,
                    "name" : "Malcolm Reynolds",
                    "href" : "/customers/210",
                    "orders" : "",
                    "orders-count" : 0
                } ]
}

(NOTE: Just an illustration, not coded to any spec like HAL, etc.)

I feel like I'm having to hand-roll all this stuff in the service and that just feels like inexperience. Surely there's a proxy-aware web framework that would allow me to create a JSON template and pass it my domain objects and expect the right results?

@Path("/customers")
public HttpResponse getAllCustomers(HttpRequest req)
{
    @Autowired CustomerRepository custRepo;
    List<Customer> data = custRepo.findAll();
    return ResponseBuilder.status(200).toJSON(data).build();
}

I understand this is probably more magic than possible, but surely there's something better than creating a HashMap for each persistent object, hard-coding each getter into a put and adding hard-coded href strings in a big galumphing loop?

user1944491
  • 559
  • 1
  • 8
  • 24
  • So are you saying that you want to return `List data` as JSON? If yes, then just set return type of Spring request mapping to `@ResponseBody List `. Include Jackson Libraries in classpath. Your list will be returned as JSON response, You can find more details [here](http://stackoverflow.com/questions/20515607/spring-mvc-responsebody-return-a-list) – Amit.rk3 Jun 05 '15 at 18:45
  • @Amit.rk3, Does this add the URLs for self and lazy has-a's? Does it avoid resolving proxies? – user1944491 Jun 07 '15 at 18:06
  • Just found a better way. Posted it in answer. I think that should work for you – Amit.rk3 Jun 07 '15 at 18:34

2 Answers2

0

I implement Json Convertor using Gson lib, so you can use with all objects not only Hibernated, but in you case you can create abstract class or interface, and make all hibernated objects implements this interface, please check this example:

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JsonConvertor {

    private static GsonBuilder gsonBuilder;
    private static Gson gson;

    private JsonConvertor() {

    }
    public static Object fromJson(String json, Class clz)
    {
        gson=new Gson();
        return gson.fromJson(json,clz);
    }

    public static String toJson(Object obj,List<String> fields) {

        gsonBuilder = new GsonBuilder();
        gsonBuilder = gsonBuilder
                .addSerializationExclusionStrategy(new CustomIclusionStrategy(fields));
        gson = gsonBuilder.create();
        String json = gson.toJson(obj);
        return json;
    }
    public static String toJson(Object obj) {

        gsonBuilder = new GsonBuilder();
        gsonBuilder = gsonBuilder
                .addSerializationExclusionStrategy(new CustomIclusionStrategy(null));
        gson = gsonBuilder.create();
        String json = gson.toJson(obj);
        return json;
    }

}

class CustomIclusionStrategy implements ExclusionStrategy {

    private List<String> fields;

    public CustomIclusionStrategy(List<String> fields) {
        this.fields = fields;
    }

    // called only if shouldSkipClass returns false
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        if(fields!=null && fields.contains(f.getName()))
        {// exclude
            return true;
        }
        // include in generating JSON
        return false;


    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        // if returns false shouldSkipField will be called, otherwise shouldSkipField will not be called
        return false;
    }
}

this is the interface:

public interface JsonConvertable {


    public String toJson();
}

this is sample object:

public class B {


    private int salary;

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }


}
 public class A {

    private int id;
    private String name;
    private B b;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }   
}

Here is an example how to exclude fields from generating JSON.

public class Test {


    public static void main(String[] args) {
        A a=new A();

        a.setId(1);
        a.setName("safwa");
        a.setB(new B());
        List<String> fields=new ArrayList<String>();
        fields.add("b");
        System.out.println("Exclusion: "+JsonConvertor.toJson(a,fields));
        System.out.println("NO Exclusion: "+JsonConvertor.toJson(a));
    }
}
Safwan Hijazi
  • 2,089
  • 2
  • 17
  • 29
  • It seems like the core of your answer is the ExclusionStrategy. Gson.toJson(Object) has no depth control so it will cycle all Proxies and resolve them. It will also not add the URLs for self and contained lists - how would you do that? How would you implement ExclusionStrategy to avoid resolving proxies? – user1944491 Jun 07 '15 at 18:13
  • what do you mean by resolving proxies, can you add example about the problem, so we can solve – Safwan Hijazi Jun 07 '15 at 18:39
  • a proxy is a essentially a partially inflated, lazy-fetched persistent object: it has only the 'id' value and will cause a database query (or 3, if my log files are to be believed) for any other value. Resolving them means doing the database queries to get all the values. Typically proxies themselves have relations, so if there's no depth-control, as in Gson, you can end up materializing half your database just for a single row of 5 columns! – user1944491 Jun 08 '15 at 11:12
  • so you need to go in depth, and generate JSON for object in relation? – Safwan Hijazi Jun 08 '15 at 11:13
  • no, Gson goes deep by default. I'd like it _not_ to do so, just return the 5 values and a link to the proxy with its `id`. I don't want it to cause any more database work. – user1944491 Jun 08 '15 at 11:42
  • thanks - I think, however, I'd prefer something more reflective if I am going to be writing this. I would probably figure out how to check for the annotation `@ManyToMany(fetch=FetchType.LAZY)` – user1944491 Jun 08 '15 at 19:56
0

Actually on top of normal JSON support, Spring provides a simple way to implement HATEOAS based rest service. Check out this link. Have a look at Greetings model class. import org.springframework.hateoas.ResourceSupport; . If you extend your POJO class from ResourceSupport. some static methods are available which lets you add these hrefs on top of your normal JSON response.

Amit.rk3
  • 2,417
  • 2
  • 10
  • 16
  • I saw this while searching but it seemed like one of those "_abandon all your existing work if you want to use this_" solutions. The dependency stack was huge and not integrated into an existing Spring solution (I fear it will break something else already in place). – user1944491 Jun 08 '15 at 11:39
  • And another thing, the example is not real-world: there are no provisions for handling/returning HTTP status codes. `Greeting` is over-annotated: it's nearly identical to hard-coding a toJson() method on each Domain object. Plus it breaks SOC: Domain objects should (do) not know about the view representation (json stream). This forces a huge (unwelcome) change in dependencies in my DAL jar. – user1944491 Jun 08 '15 at 19:52