I have a Unity 2019 WebGL project in which I am calling a REST service to return a JSON string.
When I call for the data in the editor it is returned and deserialized perfectly into a class however when I compile to WebGL the same data fails.
My JSON string is: "{"status":"success","num":29,"id":"qsmKUMuia"}"
My class is:
[Serializable]
public class JSONClass
{
public string status;
public int num;
public string id;
public JSONClass()
{
}
public JSONClass(string status, int num, string id)
{
this.status = status;
this.num = num;
this.id = id;
}
}
I am simply trying:
JSONClass jsonClass = JsonConvert.DeserializeObject<JSONClass>(data);
The error it is giving is:
Error converting value "{"status":"success","num":29,"id":"qsmKUMuia"}" to type 'JSONClass'. Path '', line 1, position 58.
What mistake am I making here? I have also tried it with JsonUtility.FromJSON but that fails as well in WebGL.
Related
I'm doing a project on Unity Webgl and getting an error only when I build the project (in the editor it works fine). Basically I'm using a Jsonblob link to assign the parameters to a custom struct I made.
The error says: Could not parse response {object}. Exception has been thrown by the target of an invocation. {object} being the json I'm using in the jsonblob website.
The struct I'm using looks like this:
public struct GameSettings
{
public String venueName { get; set; }
public Monitor[] monitors { get; set; }
public Flag[] flags { get; set; }
public InformationMonitor[] informationMonitors { private get; set; }
public Banner[] banners { get; set; }
private const int MAX_MONITOR_LENGHT = 4;
}
While the functions where I get the data and assign it look like this:
public GameSettings gameSettings;
public void Awake()
{
isRecovered = false;
GetData((response) => {
isRecovered = true;
gameSettings = response;
});
}
public async void GetData(System.Action<GameSettings> callback)
{
var url = jsonURL;
var httpClient = new HttpClient(new JsonSerializationOptions());
var result = await httpClient.Get<GameSettings>(url);
callback(result);
}
As the error says, it seems to have trouble parsing the object, although I dont really understand why that is and especially why is it happening when the project works properly in the editor, am I missing something?
As WebGL doesn't support multi-threading nor async the HttpClient is not supported on WebGL at all (also see here).
You should rather use a UnityWebrequest.Get and for the JSON deserialization either use the built-in JsonUtility or Newtonsoft Json.NET which comes as a Package and preinstalled in latest versions.
E.g. something like
// In order to use the built-in serializer your types need to be [Serializable]
[Serializable]
public struct GameSettings
{
private const int MAX_MONITOR_LENGHT = 4;
// Note that all nested types also need to be [Serializable]
// Also at least the built-in JsonUtility only supports fields by default, no properties
// so either make them all fields or use Newtonsoft
// or enforce serialization using the undocumented [field: SerializeField] for each property
public string venueName;
public Monitor[] monitors;
public Flag[] flags;
public InformationMonitor[] informationMonitors;
public Banner[] banners;
}
public GameSettings gameSettings;
public string jsonURL;
// Start can be a Coroutine, if it returns IEnumerator it is automatically
// statred as a Coroutine by Unity
private IEnumerator Start()
{
isRecovered = false;
// you can simply yield another IEnumerator
yield return GetData();
}
private IEnumerator GetData()
{
using(var www = UnityWebRequest.Get(jsonURL))
{
// Request and wait for the result
yield return www.SendWebRequest();
if(www.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"failed due to \"{www.error}\"", this);
yield break;
}
// now this depends on how good your types are (de)serializable
// either the built-in way
gameSettings = JsonUtiltiy.FromJson<GameSettings>(www.downloadHandler.text);
// or directly using Newtonsoft
gameSettings = JsonConvert.DeserializeObject<GameSettings>(www.downloadHandler.text);
isRecovered = true;
}
}
Serialization does not happen properly when I use #Json in the fields but it started working after changing to #field:Json.
I came through this change after reading some bug thread and I think this is specific to kotlin. I would like to know what difference does #field:Json bring and is it really specific to kotlin?
Whatever you put between # and : in your annotation specifies the exact target for your Annotation.
When using Kotlin with JVM there is a substantial number of things generated, therefore your Annotation could be put in many places. If you don't specify a target you're letting the Kotlin compiler choose where the Annotation should be put. When you specify the target -> you're in charge.
To better see the difference you should inspect the decompiled Java code of the Kotlin Bytecode in IntelliJ/Android Studio.
Example kotlin code:
class Example {
#ExampleAnnotation
val a: String = TODO()
#get:ExampleAnnotation
val b: String = TODO()
#field:ExampleAnnotation
val c: String = TODO()
}
Decompiled Java code:
public final class Example {
#NotNull
private final String a;
#NotNull
private final String b;
#ExampleAnnotation
#NotNull
private final String c;
/** #deprecated */
// $FF: synthetic method
#ExampleAnnotation
public static void a$annotations() {
}
#NotNull
public final String getA() {
return this.a;
}
#ExampleAnnotation
#NotNull
public final String getB() {
return this.b;
}
#NotNull
public final String getC() {
return this.c;
}
public Example() {
boolean var1 = false;
throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
}
}
For more info go to Kotlin docs.
I am working on an embedded jersey instance which will run a JAXB RESTful service. I have configured Jackson with two steps:
Adding this to my POM
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.23.2</version>
</dependency>
Registering it in my application
public HandheldApplication() {
scripts.add(HandheldServer.class);
scripts.add(BasicScript.class);
// Add JacksonFeature.
scripts.add(JacksonFeature.class);
scripts.add(LoggingFilter.class);
}
I have a complex object being passed back and forth as shown below:
package com.ziath.handheldserver.valueobjects;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.*;
#SuppressWarnings("restriction")
#XmlRootElement
public class Widget {
private String key;
private String name;
private List<String> options = new ArrayList<String>();
private String value;
private String type;
public Widget(){
super();
}
public Widget(String key, String name, List<String> options, String value,
String type) {
super();
this.key = key;
this.name = name;
this.options = options;
this.value = value;
this.type = type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getOptions() {
return options;
}
public void setOptions(List<String> options) {
this.options = options;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
When I execute this in a GET method as shown below:
#Override
#GET
#Path("getKeys")
#Produces(MediaType.APPLICATION_JSON)
public List<Widget> getKeys(#QueryParam(value = "page") int page)
This works fine and I get JSON back; however when I execute it is a PUT as shown below:
#Override
#PUT
#Path("validateKeys")
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(#QueryParam(value = "page")int page, #QueryParam(value = "widgets")List<Widget> widgets)
When I execute a PUT to access this method I get a stack trace as follows:
Caused by: org.glassfish.jersey.internal.inject.ExtractorException: Error un-marshalling JAXB object of type: class com.ziath.handheldserver.valueobjects.Widget.
at org.glassfish.jersey.jaxb.internal.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:195)
at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.convert(AbstractParamValueExtractor.java:139)
at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.fromString(AbstractParamValueExtractor.java:130)
at org.glassfish.jersey.server.internal.inject.CollectionExtractor.extract(CollectionExtractor.java:88)
at org.glassfish.jersey.server.internal.inject.CollectionExtractor$ListValueOf.extract(CollectionExtractor.java:107)
at org.glassfish.jersey.server.internal.inject.QueryParamValueFactoryProvider$QueryParamValueFactory.provide(QueryParamValueFactoryProvider.java:89)
... 38 more
Caused by: javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.]
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:335)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:563)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:249)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:214)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:140)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:123)
at org.glassfish.jersey.jaxb.internal.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:190)
... 43 more
So it seems to me that Jackson is correctly marshalling my POJO into JSON but trying to unmarshall it as XML. Note that I switched to Jackson away from MOXy because I needed to be able to handle collections coming back and forth and apparently MOXy cannot do that.
Is there a setting I've missed to tell Jackson/Jersey to go both ways for JSON?
Try removing #QueryParam(value = "widgets") because you should pass it as entity body - not query param.
#PUT
#Path("validateKeys")
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(#QueryParam(value = "page")int page, List<Widget> widgets)
Also you can make wrapper class:
#XmlRootElement
public class Widgets {
private List<Widget> widgets;
// other fields, setters and getters
}
And then:
#PUT
#Path("validateKeys")
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(#QueryParam(value = "page")int page, Widgets widgets)
I would suggest to read some discussions about REST design because you're using verbs in your paths:
Is this a bad REST URL?
Understanding REST: Verbs, error codes, and authentication
I was switching between QueryParam and FormParam to try and get one of them to work. If I use FormParam I also need to change the consumes to APPLICATION_FORM_URLENCODED.
The actual issue was that the default unmarshalling with Jackson was using XML because it was tagged as an XML resource - take that out! I finally managed to work out how to unmarshall from JSON by using a static fromString method. Then to handle the list; I cannot use a wrapper class because this needs to be highly cross language and exposing a wrapper with a list would have complicated the implementation from Python, C#, etc. The way to get it to accept a list with a wrapper is to post the name of the param (in this case widgets) multiple time. Then each JSON passed in will be called against the fromString method.
I implemented Rest api with Spring Boot. In my controller class, I have code to handle GET request which will return JSON if record found.
// SeqController.java
#Autowired
private SeqService seqService;
#RequestMapping(
value = "/api/seqs/{analysis_id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<SeqTb>> getSeqByAnalysisId(#PathVariable("analysis_id") String analysis_id) {
List<SeqTb> seqs = seqService.findByAnalysisId(analysis_id);
return new ResponseEntity(seqs, HttpStatus.OK);
}
I also create a bean class SeqServiceBean that extends the interface SeqService which in turn calls methods from the following JPA repository for query.
//SeqRepository.java
#Repository
public interface SeqRepository extends JpaRepository<SeqTb, Integer> {
#Override
public List<SeqTb> findAll();
public List<SeqTb> findByAnalysisId(String analysisId);
}
Problem is when I typed the url (http://localhost:8080/api/seqs/fdebfd6e-d046-4192-8b97-ac9f65dc2009) in my browser, it returned nothing but a pair of empty brackets. I just looked in the database and that record is indeed there. What did I do wrong?
A bit late to answer this quesiton, but in case anyone else is having this issue.
This problem may be caused by the class (that we want to be displayed as a json object) missing getter and/or setter methods.
In your case the "seqTab" class may be not have getters.
Without the getters our application can not extract the fileds to build the json object.
Example :
Sample user class
public class User {
private String firstname;
private String lasttname;
int age;
public User(){
}
public User(String fname, String lname, int age){
this.firstname = fname;
this.lasttname = lname;
this.age = age;
}
}
Sample rest controller
#RestController
public class SampleRS {
#RequestMapping(value = {"/sample/{input}"}, method = RequestMethod.GET , produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> startService(#PathVariable("input") String input){
User u = new User(input,"bikila",45);
return new ResponseEntity<User>(u,HttpStatus.OK);
}
}
// If we try to hit the endpoint /sample{input} .. e.g.
Request : localhost:8080/Sample/abebe
Response :
{}
But adding the getters for the User class will solve the problem.
Modified User class with getters
public class User {
private String firstname;
private String lasttname;
int age;
public User(){
}
public User(String fname, String lname, int age){
this.firstname = fname;
this.lasttname = lname;
this.age = age;
}
public String getFirstname() {
return firstname;
}
public String getLasttname() {
return lasttname;
}
public int getAge() {
return age;
}
}
Request : http://localhost:8080/sample/abebe
Response : {"firstname":"abebe","lasttname":"bikila","age":45}
Hope that helps !
In most of case, database driver jar is not deployed in server. Check deployment assembly of project in eclipse. Also see console message to check if it is showing driver jar not found.
If this is case simply deploy this jar in deployment assembly of eclipse.
One thing, if build path has this jdbc driverjar in eclipse, main method will connect to database. But if jar is not deployed jdbc connection will not happen over http request.
I am attempting to write a simple test class that emulates a RESTful Web Service creating a Customer via a POST method. The following fails at assertEquals, I receive a 400 Bad Request response. I cannot use debugger to observe stack trace. However the console tells me the following...
INFO: Started listener bound to [localhost:9998]
INFO: [HttpServer] Started.
public class SimpleTest extends JerseyTestNg.ContainerPerMethodTest {
public class Customer {
public Customer() {}
public Customer(String name, int id) {
this.name = name;
this.id = id;
}
#JsonProperty("name")
private String name;
#JsonProperty("id")
private int id;
}
#Override
protected Application configure() {
return new ResourceConfig(MyService.class);
}
#Path("hello")
public static class MyService {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public final Response createCustomer(Customer customer) {
System.out.println("Customer data: " + customer.toString());
return Response.ok("customer created").build();
}
}
#Test
private void test() {
String json = "{" +
"\"name\": \"bill\", " +
"\"id\": 4" +
"}";
final Response response = target("hello").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(json));
System.out.println(response.toString());
assertEquals(response.getStatus(), 200);
}
}
Instead of printing response.toString(), you can read the actual body using response.readEntity(String.class). What you will find in the body is an error message from Jackson
No suitable constructor found for type [simple type, class simple.SimpleTest$Customer]: can not instantiate from JSON object (need to add/enable type information?)
At first glance your Customer class looks ok; it has a default constructor. But the really problem is that Jackson cannot instantiate it because it is a non-static inner class. So to fix it, simply make the Customer class static.
public static class Customer {}
As a general rule, when working with JSON and Jackson with Jersey, often when you get a 400, it a a problem with Jackson, and Jackson is pretty good at spitting out a meaningful message that will help us debug.