1

I have an object that is a list of 'Level' objects and I'm testing transferring them with Spring Boot Rest Controller in 2 ways:

  1. with JSON, in Rest Controller I use something like:

     @RequestMapping(value = "/api/v1/layers/{layername}", method =         RequestMethod.GET, produces = "application/json")
     public @ResponseBody List<Level>  query(@PathVariable String layername,
                   @RequestParam("northEastLat") Float northEastLat,
                   @RequestParam("northEastLng") Float northEastLng,
                   @RequestParam("northWestLat") Float northWestLat,
                   @RequestParam("northWestLng") Float northWestLng,
    
                   @RequestParam("southEastLat") Float southEastLat,
                   @RequestParam("southEastLng") Float southEastLng,
                   @RequestParam("southWestLat") Float southWestLat,
                   @RequestParam("southWestLng") Float southWestLng
    ) {
    
    List<Level> poligons=levelService.obtainLevels(layername,southWestLng,southWestLat,northWestLng,northWestLat,northEastLng,northEastLat,southEastLng,southEastLat);
    int i=1;
    for (Level p : poligons) {
    
        System.out.println("poligon" + i++ + " is:" + p.toString());
    }
    
    return poligons;
    }
    
  2. With Protostuff Protobuf format, I use something like:

      @RequestMapping(value = "/api/v1/layers/{layername}", method = RequestMethod.GET,produces = "text/plain")
      public String query(@PathVariable String layername,
                    @RequestParam("northEastLat") Float northEastLat,
                    @RequestParam("northEastLng") Float northEastLng,
                    @RequestParam("northWestLat") Float northWestLat,
                    @RequestParam("northWestLng") Float northWestLng,
    
                    @RequestParam("southEastLat") Float southEastLat,
                    @RequestParam("southEastLng") Float southEastLng,
                    @RequestParam("southWestLat") Float southWestLat,
                    @RequestParam("southWestLng") Float southWestLng
      ) {
    
    
    List<Level> poligons=levelService.obtainLevels(layername,southWestLng,southWestLat,northWestLng,northWestLat,northEastLng,northEastLat,southEastLng,southEastLat);
    LevelList list = new LevelList(poligons);
    
    byte[] bytes;
    
    int i=1;
    for (Level p : poligons) {
    
        System.out.println("poligon" + i++ + " is:" + p.toString());
    }
    
    Schema<LevelList> schema = RuntimeSchema.getSchema(LevelList.class);
    LinkedBuffer buffer = LinkedBuffer.allocate();
    
    
    
    try
    {
        bytes = ProtostuffIOUtil.toByteArray(list, schema, buffer);
    }
    finally
    {
        buffer.clear();
    }
    
     return new String(bytes);
    }
    

The Level object format is : [{"wkb_geometry":"{"type":"Polygon","coordinates":[[[24.446822,45.34997],[24.706508,45.352485]]]}","id":199,"level":"3","type":null}

The Level object is :

@Entity(name = "Level")
@Table(name="Level2G")
@SecondaryTables({
    @SecondaryTable(name="Level3G"),
    @SecondaryTable(name="Level4G")
})
public class Level implements Serializable {

private static final long serialVersionUID = 1L;

// @Column(name = "wkb_geometry",columnDefinition="Geometry")
//@Type(type = "org.hibernate.spatial.GeometryType")
@Column(name="wkb_geometry")
private /*Geometry */ String  wkb_geometry;

@Id
@Column(name="id")
private Integer id;


@Column(name="level")
private String level;

@Transient
private String type;

public Level() {
}

public Level(String  wkb_geometry, Integer id, String level) {
    this.wkb_geometry = wkb_geometry;
    this.id = id;
    this.level = level;
    this.type = "Feature";
}

public Level(String  wkb_geometry, Integer id, String level, String type) {
    this.wkb_geometry = wkb_geometry;
    this.id = id;
    this.level = level;
    this.type = type;
}

public Object getWkb_geometry() {
    return wkb_geometry;
}

public void setWkb_geometry(String  wkb_geometry) {
    this.wkb_geometry = wkb_geometry;
}

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}

public String getLevel() {
    return level;
}

public void setLevel(String level) {
    this.level = level;
}

public String getType() {
    return type;
}

public void setType(String type) {
    this.type = type;
}

@Override
public String toString() {
    return "Level{" +
            "wkb_geometry=" + wkb_geometry +
            ", id=" + id +
            ", level='" + level + '\'' +
            ", type='" + type + '\'' +
            '}';
}
 }

The LevelList object is just a List of Level objects

The problem is that with Protostuff I get a bigger payload (26 kb) comparing to JSON (3.7kb). Why?

Also for second option I also tried setting "application/octet-stream" to return bytes directly but still the same result. Also I compared speed for both JSON and protobuf; protobuf has the better performance even with a bigger payload. Any idea why?

jasonlam604
  • 1,456
  • 2
  • 16
  • 25
Jolie
  • 33
  • 7
  • for second option I tried also produces ="application/octet-stream" to return bytes directly but still same result – Jolie Jul 07 '16 at 14:07
  • could you copy/paste here the actual objects you're serializing? Maybe one format is serializing more information than the other? – Brian Clozel Jul 07 '16 at 15:15
  • I don't know if it will help you much, but: https://spring.io/blog/2015/03/22/using-google-protocol-buffers-with-spring-mvc-based-rest-services – Software Engineer Jul 08 '16 at 22:46

2 Answers2

2

Protostuff and Protobuf are not the same thing. Protostuff is a wrapper library that can use many different serialization formats. It also supports runtime schema generation, which you appear to be using. That runtime schema requires sending extra metadata along with the message to tell the receiver about the message's schema. I would guess that the large message you're seeing is mostly from this runtime schema data.

With standard Protobuf, the schema is not sent with the message because it is assumed that the sender and recipient already agree on a schema provided by a .proto file compiled into both programs. If you use Protobuf with a standard .proto file, you'll find that the messages it produces are much smaller than JSON.

Kenton Varda
  • 41,353
  • 8
  • 121
  • 105
0

You have at least one problem in your test.

This transformation from byte array to String is not valid:

bytes = ProtostuffIOUtil.toByteArray(list, schema, buffer);
return new String(bytes);

This constructor of String will try to parse byte array as a UTF-8 string (most probably; depends on your locale settings), but given data by definition is not valid UTF-8 string.

If you want to make better size comparison, you should write a test in a following form:

LevelList source = testData();
byte[] jsonData = generateJson(source);
byte[] protobufData = generateProtobuf(source);
System.out.println("JSON=" + jsonData.size() + " Protobuf=" + protobufData.size());

Main point here is to make your test reproducible, so that other people can repeat it.

Kostiantyn
  • 935
  • 8
  • 13
  • Thanks . But like i said ,for second option I tried also produces ="application/octet-stream" to return bytes directly but still same result. Also I compared speed for json and protobuf and protobuf has better speed even if higher payload. Any idea why? – Jolie Jul 08 '16 at 07:12
  • Theoretically it is not possible, binary protobuf encoding is more compact than JSON by definition. – Kostiantyn Jul 09 '16 at 09:09