turoni turoni - 6 months ago 49
JSON Question

Upload pdf in HTML and Deserialize json file

I'm trying to upload a file in html and then send it to my database via restangular.
My frontend is a combination of angular with typescript but the upload is a form.

<form enctype="multipart/form-data">
<fieldset class="form-group" ng-repeat="field in $ctrl.metadata.fields">
<label ng-if="field.inputType !== 'hidden'" for="{{field.propertyKey}}"><strong>{{field.name}}</strong></label>
<input ng-if="field.inputType !== 'select' && field.inputType !== 'file'" class="form-control" type="{{field.inputType}}" name="{{field.propertyKey}}" id="{{field.propertyKey}}" ng-model="$ctrl.data[field.propertyKey]"/>
<input ng-if="field.inputType === 'file'" class="form-control" ngf-select type="{{field.inputType}}" name="{{field.propertyKey}}" id="{{field.propertyKey}}" ng-model="$ctrl.data[field.propertyKey]"/>
<sp-dropdown ng-if="field.inputType === 'select'" value="$ctrl.data[field.propertyKey]" api-domain="field.linkedObjectApiDomain" linked-object-name="field.linkedObjectName"></sp-dropdown>
</fieldset>
<button class="btn btn-primary" ng-click="$ctrl.save({item: $ctrl.data})">Save</button>
<button ng-if="$ctrl.metadata.buttons.hasOpen" class="btn btn-primary" ng-click="$ctrl.open()">Open</button>
</form>


I did the databinding of the file with ng-file-upload.

Upon saving we enter this typescript save method.

public save(item: any): any {
console.log("item to save is ", item);
console.log("rapport is ", item["rapport"]);

if (item.id === undefined) {
this.restService.save(this.metadata.apiDomain, item).then((addedItem: any) => {
toastr.success(`${addedItem.naam} successfully created.`, `Overzicht Dossiers Created`);
});
} else {
this.restService.update(this.metadata.apiDomain, item).then((updatedItem: any) => {
toastr.success(`${updatedItem.naam} successfully updated.`, `Overzicht Dossiers Updated`);
});
}
}


The second log with the file gives the json:


lastModified:1463402787393
lastModifiedDate:Mon May 16 2016 14:46:27 GMT+0200 (Romance (zomertijd))
name:"Rapport.pdf"
size:83605
type:"application/pdf"
upload:Promise
webkitRelativePath:""
__proto__:File


On server side I'm using a spring project which I didn't set up myself but the important files are my class which should store this data
Dossier

/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package be.ugent.lca.data.entities;

import be.ugent.sherpa.entity.BaseEntity;
import java.sql.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;

/**
*
* @author Sam
*/
@Entity
//@JsonDeserialize(using = DossierDeserializer.class)
//@JsonSerialize(using = DossierSerializer.class)
public class Dossier extends BaseEntity{
private String externDossierNr;
private String internDossierNr;
private Date datum;
private Boolean doc;
private Date refKlantDatum;
private String refKlantVerwijzing;
private String verantw;


@OneToOne(fetch=FetchType.LAZY, mappedBy="dossier")
private Offerte offerte;

private String status;

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name = "persoon")
private Persoon persoon;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "OrganisatieFirma")
private OrganisatieFirma organisatieFirma;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "OrganisatieIntern")
private OrganisatieIntern organisatieIntern;

@Lob
@Column(length=100000)
private byte[] rapport;

public Offerte getOfferte() {
return offerte;
}

public void setOfferte(Offerte offerte) {
this.offerte = offerte;
}

public byte[] getRapport() {
return rapport;
}

public void setRapport(byte[] rapport) {
this.rapport = rapport;
}

public OrganisatieFirma getOrganisatieFirma() {
return organisatieFirma;
}

public String getExternDossierNr() {
return externDossierNr;
}

public void setExternDossierNr(String externDossierNr) {
this.externDossierNr = externDossierNr;
}

public String getInternDossierNr() {
return internDossierNr;
}

public void setInternDossierNr(String internDossierNr) {
this.internDossierNr = internDossierNr;
}

public void setOrganisatieFirma(OrganisatieFirma organisatieFirma) {
this.organisatieFirma = organisatieFirma;
}

public OrganisatieIntern getOrganisatieIntern() {
return organisatieIntern;
}

public void setOrganisatieIntern(OrganisatieIntern organisatieIntern) {
this.organisatieIntern = organisatieIntern;
}

public Persoon getPersoon() {
return persoon;
}

public void setPersoon(Persoon persoon) {
this.persoon = persoon;
}

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

public Date getDatum() {
return datum;
}

public void setDatum(Date datum) {
this.datum = datum;
}

public Date getRefKlantDatum() {
return refKlantDatum;
}

public void setRefKlantDatum(Date refKlantDatum) {
this.refKlantDatum = refKlantDatum;
}

public String getRefKlantVerwijzing() {
return refKlantVerwijzing;
}

public void setRefKlantVerwijzing(String refKlantVerwijzing) {
this.refKlantVerwijzing = refKlantVerwijzing;
}

public String getVerantw() {
return verantw;
}

public void setVerantw(String verantw) {
this.verantw = verantw;
}

public Boolean getDoc() {
return doc;
}

public void setDoc(Boolean doc) {
this.doc = doc;
}

}




and my repository for this class

/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package be.ugent.lca.data.repository;

import be.ugent.lca.data.entities.Dossier;
import be.ugent.lca.data.query.DossierQuery;
import be.ugent.sherpa.repository.RestRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
/**
*
* @author Sam
*/
@RepositoryRestResource(collectionResourceRel = "dossiers", path = "dossiers")
public interface DossierRepository extends RestRepository<Dossier, DossierQuery<?>>{

}


When trying to save a file to my database the server gives this exception
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of byte[] out of START_OBJECT token

This led me to believe that I have to write my own deserializer for Dossier
Thus:

package be.ugent.lca.data.entities.deserializers;


import be.ugent.lca.data.entities.Dossier;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;

public class DossierDeserializer extends JsonDeserializer {
@Override
public Dossier deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode root = oc.readTree(jsonParser);
Dossier dossier = new Dossier();

dossier.setExternDossierNr(root.get("externDossierNr").asText());
dossier.setInternDossierNr(root.get("internDossierNr").asText());

return dossier;
}
}


But my problem is that I don't know how exactly to deserialize the file json, since writing out root.get("rapport") gives back an empty string.

Any help would be much appreciated.

Answer

I've worked out the file upload.

First of all I split the file upload from the rest of my data so I won't have to rewrite the automatic deserialization for everything that does work.

this.restService.save(this.metadata.apiDomain, item).then((addedItem: any) => {
    toastr.success(`${addedItem.naam} successfully created.`, `Overzicht Dossiers Created`);
    console.log("created item ", addedItem);
    var fd = new FormData();
    fd.append("rapport", item["rapport"]);

    this.restService.one('dossiers/' + addedItem.id + '/rapport').withHttpConfig({transformRequest: angular.identity}).customPOST(fd, '', undefined, {'Content-Type': undefined}).then(
        (addedDossier: any) => {
            console.log("posted dossier ", addedDossier);
        }
    );
});

In the callback of my normal save I do the custom post to dossiers/{id}/rapport for this I need a custom controller.

@BasePathAwareController
@RequestMapping("/dossiers/{id}")
@ExposesResourceFor(Dossier.class)
public class DossierController {

The BasePathAwawareController makes sure that all automatically generated paths that you don't override keep existing.

@Autowired
private DossierRepository dossierRepository;

With this I inject my repository to connect to my database.

@RequestMapping(path = "/rapport", method = RequestMethod.POST)//,headers = "content-type=multipart/form-data") 
public @ResponseBody String postRapport(@PathVariable("id") Long id,@RequestParam("rapport") MultipartFile file) {
    String name = "rapport";
    System.out.println("Entered custom file upload with id " + id);
    if (!file.isEmpty()) {
        try {
            byte[] bytes = file.getBytes();
            Dossier dossier = dossierRepository.findOne(id);
            dossier.setRapport(bytes);

            dossierRepository.save(dossier);
            return "You successfully uploaded " + name + " into " + name + "-uploaded !";
        } catch (Exception e) {
            return "You failed to upload " + name + " => " + e.getMessage();
        }
    } else {
        return "You failed to upload " + name + " because the file was empty.";
    }
}

Like this I'm able to successfully upload my file.

Comments