I am writing a REST interface using Jersey and have configured it to use JacksonXML to do the JSON<->POJO conversions. I have a set of classes like this:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonPo {
public Long id;
public String type;
public Boolean committed;
public String owner;
// ...
Which is referenced in my main class like this:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonPoReq {
public Long id;
public String pathstring;
public int firstrecord;
public JsonPo obj;
// ...
And the REST resource of course looks like this:
#POST
#Path("path")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response byPath(JsonPoReq req) {
// ...
The serialization (POJO->JSON) works fine. The properties in the JsonPo object get serialized in a JSON object as you would expect.
The reverse doesn't work. Even if I feed the serialized string back. What happens is the obj property is always set to null.
EDIT: As requested the following is what the JSON string looks like:
{
"id" : 2,
"pathstring" : "/a/b/c",
"firstrecord" : 0,
"obj" : {
"id" : 2,
"type" : "ProtoObject",
"committed" : false,
"owner" : "admin"
}
}
I have been going through the JacksonXML Annotations to see how to fix this but I haven't been able to come up with anything. Is there some other step I am supposed to take?
UPDATE: Problem was not real. Some application code was being invoked that I didn't notice was clearing the object.
Related
I have the following class
public class WorkpoolId implements Serializable {
#NotNull
private Long id = null;
#JsonCreator
public WorkpoolId(#JsonProperty("workpoolId") long id) {
this.id = Long.valueOf(id);
}
public WorkpoolId(Long id) {
this.id = id;
}
public WorkpoolId(String id) {
this.id = Long.valueOf(id);
}
private WorkpoolId() {
}
}
when mapping
"workpoolId":1
to this class I get a
javax.ws.rs.ProcessingException: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of WorkpoolId (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)
Why is jackson not able to use the long constructor for the number value?
It fails because your WorkpoolId does not have access to field workpoolId it is not in its context anuymore. When your JSON is deserialized it could be deserialized either as an
independent object (having no field workpoolId, it IS the workbookId)
field object value in an object containing -say Data - it that might be named as workpoolId.
Now the use of workbookId could be usable for the JsonCreator in Data when constructing its field workpoolId.
To clarify this a bit, here is an example of possible Data class:
#Getter #Setter
public class Data {
private WorkpoolId workpoolId;
#JsonCreator // here it is a property!
public Data(#JsonProperty("workpoolId") long id) {
this.workpoolId = new WorkpoolId(id);
}
}
Json would be like {"workpoolId":1}
To have it work just remove the annotation #JsonProperty("workpoolId") from the attribute declaration. Actually the whole #JsonCreator annotation is not needed.
I'm trying to create a ViewComponent that I can use with TagHelpers where I pass in an object as the parameter.
So, for example, I have this ViewComponent—which could end up having more parameters:
public IViewComponentResult Invoke(string? pageTitle, string? description, string? headerView)
And I'd like to do something like:
public class PageHeaderViewComponentOptions
{
public string pageTitle2;
public string Description;
}
public IViewComponentResult Invoke(PageHeaderViewComponentOptions phvc)
And use it like:
<vc:page-header phvc:page-title2="Test Title"></vc:page-header>
But, this isn't working.
I also tried explicitly setting the attribute information on the parameter class as follows:
[HtmlTargetElement("PageHeaderViewController")]
public class PageHeaderViewComponentOptions
{
[HtmlAttributeName("page-title")]
public string pageTitle2 { get; set; }
public string Description;
}
With no luck either.
My thought is that I could pass that object directly to the view. Typing this, I wonder if passing in the parameters separately and then assigning each to a member of a specific view model might be a better idea.
Any help here would be greatly appreciated.
In order to add an object as a parameter you'll need to pass an object.
This object can be created in the controller and passed as part of the view model, e.g.:
#model MyApp.MyViewModel
Where MyViewModel is something like:
public class MyViewModel
{
public PageHeaderViewComponentOptions PhvcOptions { get; set; }
}
And you pass the object like this:
<vc:page-header phvc="PhvcOptions"></vc:page-header>
Inside of a razor code block:
#{
var PhvcOptions = new PageHeaderViewComponentOptions
{
pageTitle2 = "Test Title";
};
}
Where you pass the object like this:
<vc:page-header phvc="#PhvcOptions"></vc:page-header>
Or even inline, which looks something like this:
<vc:page-header phvc="#(new PageHeaderViewComponentOptions { pageTitle2 = "Test Title" })"></vc:page-header>
Though that defeats the purpose and string input should be used instead:
<vc:page-header pageTitle2="Test Title"></vc:page-header>
The advantage of an object is that it may be null and fields of the object may be omitted. Where string parameters need to be present in the tag, even when empty.
Inserting objects is IMO also useful when you can use a part of the view model as a parameter, as shown above, or when the view model implements different interfaces and can therefore be passed as the parameter itself:
<vc:page-header phvc="Model"></vc:page-header>
Where MyViewModel is something like:
public class MyViewModel : IPageHeaderViewComponentOptions
{
public string pageTitle2 { get; set; }
}
And
public interface IPageHeaderViewComponentOptions
{
string pageTitle2;
}
I didn't test the code and there may be typos. Especially with the #. But you get the idea. Either approach should work.
I'm using the [FromQuery] attribute to parse a Get requests arguments into a complex object. For example:
[HttpGet("some-get-request")]
public IActionResult DoSomething([FromQuery] SomeArguments someArgs)
{
}
One of the properties of the SomeArguments object is an enum.
public enum SomeEnum { EnumValue01, EnumValue02 }
public class SomeArguments
{
[FromQuery(Name = "enum_arg")]
public SomeEnum EnumArgument { get; set; }
}
And I call the endpoint with something like:
http://localhost:1234/api/some-controller/some-get-request?enum_arg=EnumValue01
And this all works great. However, I want to be able to use a different enum value in the URL than in my C# enum value. For example, I want to call using a URL such as
http://localhost:1234/api/some-controller/some-get-request?enum_arg=eval01
How can I do this?
I thought I could use the [FromQuery] attribute, like I can with properties, but that doesnt seem to be possible:
'FromQuery' is not valid on this declaration type. It is only valid on 'property, indexer, parameter'
You can use EnumMemberAttribute in conjunction with StringEnumConverter to achieve your goal. Define SomeEnum as following
[JsonConverter(typeof(StringEnumConverter))]
public enum SomeEnum
{
[EnumMember(Value = "eval01")]
EnumValue01,
[EnumMember(Value = "eval02")]
EnumValue02
}
At this point it will work as you need only when Newtonsoft json serializer is used. For example, when controller expects POST request and parameter is marked as [FromBody]. In your case it won't work yet because during binding of [FromQuery] parameter json serializer is not used. To solve this one create custom model binder
public class JsonModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string rawData = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue;
rawData = JsonConvert.SerializeObject(rawData); //turns value to valid json
try
{
SomeEnum result = JsonConvert.DeserializeObject<SomeEnum>(rawData); //manually deserializing value
bindingContext.Result = ModelBindingResult.Success(result);
}
catch (JsonSerializationException ex)
{
//do nothing since "failed" result is set by default
}
return Task.CompletedTask;
}
}
Update SomeEnum definition to use JsonModelBinder
[JsonConverter(typeof(StringEnumConverter))]
[ModelBinder(typeof(JsonModelBinder))]
public enum SomeEnum
Faced with problem. Some fields in my classes are injected and in debugger i can see something like this:
Problem begins when I am trying to map #Aspect to one of methods defined in SettingService. Like this:
#Aspect
public class SettingsAspect
{
#AfterReturning(pointcut = "execution( * package.SettingsService.method(..))", returning = "result")
public void profilingSettingsAdvice(JoinPoint joinPoint, String result)
{
System.out.println(joinPoint.getArgs());
}
}
My service looks like this:
#Service
#Transactional
public class SettingsService
{
#Cacheable(value = "DefaultSettingsCache", key = "#root.methodName")
public int method()
{
return 1;
}
}
Don't know why, aspect isn't called after method() execution. Mystery is that aspect works ok with other classes/ What does it mean when class is injected with type Blablabla$$EnhancerBySpringCGLIB?
Thank you.
Your advice only matches methods returning a String, but your method returns an int.
I am working on an API that needs to return an array of enums. I am using JSON.NET with WebAPI, and while the StringEnumConverter is sufficient to convert properties which are enums into their string values, it doesn't convert a result which is just an array of enums, instead it returns just the integer value.
So if my endpoint looks like this:
[RoutePrefix("Items")]
public class ItemsController : ApiController
{
[HttpGet][Route("")]
public IHttpActionResult Get()
{
var items = Enum.GetValues(typeof(Items))
.Cast<Items>()
.ToList()
return Ok(items);
}
}
public enum Items
{
First = 0,
Second = 1,
Third = 2
}
Then a call to GET /Items currently returns [ 0, 1, 2 ]; what I would like to get back is [ "First", "Second", "Third" ].
What I don't want to have to do is put a wrapper around the result :
public class ItemsList
{
[JsonProperty("Items", ItemConverterType=typeof(StringEnumConverter))]
public List<Items> Items { get;set;
}
which, while it might technically work, would result in this endpoint being inconsistent with the rest of the API, which doesn't require wrappers round its results.
Try to add StringEnumConverter into your WebApiConfig
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter());
//...........................................
}