ControllerAdvice for Kotlin - spring

I want to create ControllerAdvice for validation exception and I'm using Webflux, Kotlin and jackson-module-kotlin.
I've tried to do it with traditional code as following :
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationExceptions(
ex: MethodArgumentNotValidException): Map<String, String?>? {
val errors: MutableMap<String, String?> = HashMap()
ex.bindingResult.allErrors.forEach(Consumer { error: ObjectError ->
val fieldName = (error as FieldError).field
val errorMessage = error.getDefaultMessage()
errors[fieldName] = errorMessage
})
return errors
}
but it doesn't work properly and the default response is :
{
"timestamp": "2020-07-25T10:19:00.023+00:00",
"path": "/boarding/subscribeUserWithSoloWorkspace",
"status": 400,
"error": "Bad Request",
"message": "Failed to read HTTP message",
"requestId": "62f1e90a-1",
"trace": "org.springframework.core.codec.DecodingException: JSON decoding error: Instantiation of [simple type, class co.ashiyane.flare.domains.User] value failed for JSON property mobileNumber due to missing (therefore NULL) value for creator parameter mobileNumber which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class co.ashiyane.flare.domains.User] value failed for JSON property mobileNumber due to missing (therefore NULL) value for creator parameter mobileNumber which is a non-nullable type\n at [Source: (io.netty.buffer.ByteBufInputStream); line: 4, column: 5] (through reference chain: co.ashiyane.flare.domains.supdomains.UserAndWorkspace[\"user\"]->co.ashiyane.flare.domains.User[\"mobileNumber\"])\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:215)\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:173)\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.lambda$decodeToMono$1(AbstractJackson2Decoder.java:159)\n\tat reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118)\n\tat reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:96)\n\tat reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287)\n\tat reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:330)\n\tat reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782)\n\tat reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:152)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)\n\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)\n\tat reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:427)\n\tat reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:210)\n\tat reactor.netty.channel.FluxReceive.request(FluxReceive.java:121)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:155)\n\tat reactor.core.publisher.FluxPeek$PeekSubscriber.request(FluxPeek.java:130)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:155)\n\tat reactor.core.publisher.MonoCollect$CollectSubscriber.onSubscribe(MonoCollect.java:112)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:86)\n\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onSubscribe(FluxPeek.java:163)\n\tat reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:86)\n\tat reactor.netty.channel.FluxReceive.startReceiver(FluxReceive.java:300)\n\tat reactor.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:138)\n\tat io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)\n\tat io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)\n\tat io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:384)\n\tat io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)\n\tat io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)\n\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\n\tat java.base/java.lang.Thread.run(Thread.java:832)\nCaused by: com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class co.ashiyane.flare.domains.User] value failed for JSON property mobileNumber due to missing (therefore NULL) value for creator parameter mobileNumber which is a non-nullable type\n at [Source: (io.netty.buffer.ByteBufInputStream); line: 4, column: 5] (through reference chain: co.ashiyane.flare.domains.supdomains.UserAndWorkspace[\"user\"]->co.ashiyane.flare.domains.User[\"mobileNumber\"])\n\tat com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:112)\n\tat com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:202)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:490)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)\n\tat com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:535)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:419)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)\n\tat com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2057)\n\tat com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1431)\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:168)\n\t... 29 more\n"
}
In the message, there are some other exceptions like DecodingException and MissingKotlinParameterException, but I could't handle them too!

I've resolved this issue with following ControllerAdvice:
#ExceptionHandler(value = [ServerWebInputException::class])
#ResponseBody
fun onException(exception: ServerWebInputException): Mono<ResponseEntity<ClientAcknowledgement>> {
val parameterName = (exception.rootCause as MissingKotlinParameterException).parameter.name // id
val parameterType = (exception.rootCause as MissingKotlinParameterException).parameter.type // ObjectId
val fieldName = (exception.rootCause as MissingKotlinParameterException).path[0].fieldName // in User part
return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ClientAcknowledgement("there is a missing parameter in your request, check your request body." +
" detail : missing $parameterName ($parameterType) type in $fieldName")))
}

Related

Jackson + KotlinModule: Conflicting/ambiguous property name definitions (implicit name 'isFoo')

Stumbling through a strange behaviour in Jackson when used with KotlinModule. Trying to deserialize a JSON object with isXxx-Boolean and xxx-none-Boolean property. Any solution how to deal with this?
data class FooObject(
#JsonProperty("isFoo")
val isFoo: Boolean,
#JsonProperty("foo")
val foo: String,
)
#Test
fun `deserialization should work` (){
val serialized = """
{
"isFoo": true,
"foo": "bar"
}
""".trimIndent()
val objectMapper: ObjectMapper = Jackson2ObjectMapperBuilder()
.modules(KotlinModule())
.build()
val deserialized = objectMapper.readValue(serialized, FooObject::class.java)
assertNotNull(deserialized)
}
throws
Results in
java.lang.IllegalStateException: Conflicting/ambiguous property name definitions (implicit name 'isFoo'): found multiple explicit names: [isFoo, foo], but also implicit accessor: [method org.dnltsk.Test$FooObject#getFoo()][visible=true,ignore=false,explicitName=false], [method org.dnltsk.Test$FooObject#isFoo()][visible=true,ignore=false,explicitName=false]
By removing the #JsonProperty-annotations the exception turns to
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Duplicate creator property "isFoo" (index 0 vs 1) for type `org.dnltsk.Test$FooObject`
at [Source: (String)"{
"isFoo": true,
"foo": "bar"
}"; line: 1, column: 1]
Add the following annotation to the top of your data class:
#JsonAutoDetect(
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
)

passing json in json to spring controller

I am trying to pass json object to spring controller and I manage to do that, but value of one property is in json and I think that I have problem because of it. But there is no other way to pass that data. Code is below,
data class:
#Entity
data class Section(
#Id
#GeneratedValue
val id: Long = 0L,
val name: String = "",
var text: String,
#ManyToOne
var notebook: Notebook
)
Controller code:
#PutMapping("/sections/{id}")
fun updateSection(#RequestBody section: Section, #PathVariable id: Long): Section =
sectionRepository.findById(id).map {
it.text = section.text
it.notebook = section.notebook
sectionRepository.save(it)
}.orElseThrow { SectionNotFoundException(id) }
javascript sending post to api:
function updateApi(data) {
axios.put(MAIN_URL + 'sections/' + data.id, {
data
})
.then(showChangesSaved())
.catch(ShowErrorSync());
}
function saveSection() {
var data = JSON.parse(window.sessionStorage.getItem("curr-section"));
data.text = JSON.stringify(element.editor).toString();
updateApi(data);
}
I get error like this:
2020-11-18 15:06:24.052 WARN 16172 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Instantiation of [simple type, class org.dn.model.Section] value failed for JSON property text due to missing (therefore NULL) value for creator parameter text which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class org.dn.model.Section] value failed for JSON property text due to missing (therefore NULL) value for creator parameter text which is a non-nullable type
at [Source: (PushbackInputStream); line: 1, column: 375] (through reference chain: org.dn.model.Section["text"])]
so text in element.editor is JSON formatted string and I need to pass it as it is to controller. Is there any way to do that? I tried searching, but I can't find json in json help...
Whole project is available on github
What does your json looks like? If I check out your project and run the following two tests:
one with Section as an object as request body
one with Section as json
Both will succeed. So the problem might lie in your JSON:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class HttpRequestTest {
#LocalServerPort
private val port = 0
#Autowired
private val restTemplate: TestRestTemplate? = null
#Test
fun sectionAsObject() {
val section = Section(0L, "2L", "text", Notebook(1L, "1", "2"))
assertThat(restTemplate!!.put("http://localhost:$port/sections/123", section
)).isNotNull
}
#Test
fun sectionAsJson() {
val sectionAsJson = """
{
"id": 0,
"name": "aName",
"text": "aText",
"noteBook": {
"id": 0,
"name": "aName",
"desc": "2"
}
}
""".trimIndent()
assertThat(restTemplate!!.put("http://localhost:$port/sections/123", sectionAsJson
)).isNotNull
}
}
BTW: it is not a pretty good habit to expose your database ids, which is considered to be a security risk as it exposes your database layer. Instead, you might want to use a functional unique key ;)

How I can return my custom json file instead of default json file that generates spring boot?

I have a rest controller for authorization:
#RestController
class AuthController {
#PostMapping("/sign-up")
fun signUp(#RequestBody signUpRequest: SignUpRequest): ResponseEntity<String> {
some code here..
}
}
The signUp method gets SignUpRequest model as a request body. SignUpRequest model is:
enum class Role {
#JsonProperty("Student")
STUDENT,
#JsonProperty("Tutor")
TUTOR
}
data class SignUpRequest(
val role: Role,
val email: String,
val password: String
)
When I make /sign-up post request with JSON:
{
"role": "asdf",
"email": "",
"password": ""
}
It returns me an answer that were generated by spring boot:
{
"timestamp": "2020-02-12T05:45:42.387+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]\n at [Source: (PushbackInputStream); line: 3, column: 10] (through reference chain: foo.bar.xyz.model.SignUpRequest[\"role\"])",
"path": "/sign-up"
}
Question is: How I can return my custom JSON instead of that default generated JSON?
I want to return my custom JSON, like:
{
"result": "Invalid user data are given",
"errors": [
{
"fieldName": "ROLE",
"text": "Given role does not exist"
},
{
"fieldName": "EMAIL",
"text": "EMAIL is empty"
}
]
}
I suggest you to create ErrorContrller that generates custom json map as response. Then when you will catch an error in sign-up method, call ErrorContrllers method.
You can find info from this link
Finally I found out a solution. You should create a class that annotates #ControllerAdvice, and make a method that annotates #ExceptionHandler.
#ControllerAdvice
class HttpMessageNotReadableExceptionController {
#ExceptionHandler(HttpMessageNotReadableException::class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleException(
exception: HttpMessageNotReadableException
): PostSignUpResponseError {
val errors = mutableListOf<PostSignUpResponseErrorItem>()
errors.add(
PostSignUpResponseErrorItem(
fieldNamePost = "Role",
text = "Given role does not exist"
)
)
return PostSignUpResponseError(
result = "Invalid user data are given",
errors = errors
)
}
}
where PostSignUpResponseErrorItem and PostSignUpResponseError are:
data class PostSignUpResponseError(
val result: String,
val errors: List<PostSignUpResponseErrorItem>
)
class PostSignUpResponseErrorItem(
val fieldNamePost: PostSignUpRequestFieldName,
val text: String
)
Anyway, I still don't know how to attach this thing to a certain PostMapping method.

Optional query string enum parameter - openapi, springboot

I have an OpenApi spec:
paths:
/lessons:
get:
tags:
- lesson
operationId: getLessons
parameters:
- in: query
name: daysOfWeek
schema:
type: array
items:
$ref: '#/components/schemas/DaysOfWeekEnum'
Using swagger codegen this generates an endpoint like:
#ApiOperation(value = "Get a collection lessons", nickname = "getLessons", notes = "", response = LessonDto.class, responseContainer = "List", tags={ "lesson", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "List of Lessons", response = LessonDto.class, responseContainer = "List") })
#RequestMapping(value = "/lessons",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<List<LessonDto>> _getLessons(#ApiParam(removed for brevity) #Valid #RequestParam(value = "daysOfWeek", required = false, defaultValue="new ArrayList<>()") List<DaysOfWeekEnum> daysOfWeek) {
return getLessons(daysOfWeek);
}
I use TestRestTemplate in a test like so:
ResponseEntity<List<LessonDto>> lessonDtos =
testRestTemplate.exchange("/lessons", HttpMethod.GET, null,
new ParameterizedTypeReference<List<LessonDto>>() {
});
This url works:
/lessons?daysOfWeek=THURSDAY
These urls do not:
/lessons
/lessons?daysOfWeek=SOME_INVALID_VALUE
...and I get the following error:
nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.util.ArrayList out of START_OBJECT
token
Any help appreciated.
In order to give you an empty list, you need to set the default value to be as an empty string:
#RequestParam(value = "daysOfWeek",
required = false,
defaultValue = "") List<DaysOfWeekEnum> daysOfWeek)
So the issue was related to an openapi-generator bug. Summary being:
When parameters of type array are added to an operation, the generated
Spring code includes an invalid defaultValue in the Spring MVC
parameter annotations
The fix was to upgrade to a later version of openapi-generator - 4.0.0 did the trick for me.
As an aside, the error message:
exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.util.ArrayList out of START_OBJECT token
..was a bit of a red herring and it was actually TestRestTemplate related i.e. the ParameterizedTypeReference part. Changing this to String.class identified the true nature of the error.

Web API/JsonMediaTypeFormatter accepts Invalid JSON and passes null argument to action

I have the following model:
public class Resource
{
[DataMember(IsRequired = true)]
[Required]
public bool IsPublic { get; set; }
[DataMember(IsRequired = true)]
[Required]
public ResourceKey ResourceKey { get; set; }
}
public class ResourceKey
{
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemId { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemDataIdType { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemEntityType { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemDataId { get; set; }
}
I have the following action method signature:
public HttpResponseMessage PostResource(Resource resource)
I send the following request with JSON in the body (an intentionally invalid value for property "IsPublic"):
Request Method:POST
Host: localhost:63307
Connection: keep-alive
Content-Length: 477
User-Agent: Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.97 Safari/537.22
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
{
"IsPublic": invalidvalue,
"ResourceKey":{
"SystemId": "asdf",
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
This is invalid JSON - run it through JSONLint and it tells you:
Parse error on line 2:
{ "IsPublic": invalidvalue,
.................^ Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '['
The ModelState.IsValid property is 'true' - WHY???
Also, instead of throwing a validation error, the formatter seems to give up on deserializing and simply passes the 'resource' argument to the action method as null!
Note that this also happens if I put in an invalid value for other properties, e.g. substituting:
"SystemId": notAnObjectOrLiteralOrArray
However, if I send the following JSON with a special undefined value for the "SystemId" property:
{
"IsPublic": true,
ResourceKey:{
"SystemId": undefined,
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
Then I get the following, reasonable, exception thrown:
Exception Type: Newtonsoft.Json.JsonReaderException
Message: "Error reading string. Unexpected token: Undefined. Path 'ResourceKey.SystemId', line 4, position 24."
Stack Trace: " at Newtonsoft.Json.JsonReader.ReadAsStringInternal()
at Newtonsoft.Json.JsonTextReader.ReadAsString()
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)"
SO: what is going on in the Newtonsoft.Json library which results in what seems like partial JSON Validation???
PS: It is possible to post JSON name/value pairs to the Web API without enclosing the names in quotes...
{
IsPublic: true,
ResourceKey:{
SystemId: "123",
SystemDataIdType: "int",
SystemDataId: "Lorem ipsum",
SystemEntityType:"EntityType"
},
}
This is also invalid JSON!
OK - so it appears that part of the problem was caused by my own doing.
I had two filters on the controller:
Checks whether there are any null action parameters being passed to an action method and if so, returns a "400 Bad Request" response stipulating that the parameter cannot be null.
A ModelState inspection filter which checked the Errors of the ModelState and if any are found, return them in a "400 Bad Request" response.
The mistake I made was to put the null argument filter before the model state checking filter.
After Model Binding, the serialization would fail correctly for the first JSON example, and would put the relevant serialization exception in ModelState and the action argument would remain null, rightfully so.
However, since the first filter was checking for null arguments and then returning a "404 Bad Request" response, the ModelState filter never kicked in...
Hence it seemed that validation was not taking place, when in fact it was, but the results were being ignored!
IMPORTANT: Serialization exceptions that happen during Model Binding are placed in the 'Exception' property of the ModelState KeyValue pair Value...NOT in the ErrorMessage property!
To help others with this distinction, here is my ModelValidationFilterAttribute:
public class ModelValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid) return;
// Return the validation errors in the response body.
var errors = new Dictionary<string, IEnumerable<string>>();
foreach (KeyValuePair<string, ModelState> keyValue in actionContext.ModelState)
{
var modelErrors = keyValue.Value.Errors.Where(e => e.ErrorMessage != string.Empty).Select(e => e.ErrorMessage).ToList();
if (modelErrors.Count > 0)
errors[keyValue.Key] = modelErrors;
// Add details of any Serialization exceptions as well
var modelExceptions = keyValue.Value.Errors.Where(e => e.Exception != null).Select(e => e.Exception.Message).ToList();
if (modelExceptions.Count > 0)
errors[keyValue.Key + "_exception"] = modelExceptions;
}
actionContext.Response =
actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
}
}
And here is the action method, with the filters in the correct order:
[ModelValidationFilter]
[ActionArgNotNullFilter]
public HttpResponseMessage PostResource(Resource resource)
So now, the following JSON results in:
{
"IsPublic": invalidvalue,
"ResourceKey":{
"SystemId": "asdf",
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
{
"resource.IsPublic_exception": [(2)
"Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21.",
"Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21."
]-
}
However, all of this does not explain why invalid JSON is still parsed by the JsonMediaTypeFormatter e.g. it does not require that names be strings.
More of a workaround than an answer, but I was able to get this to work using the workaround posted at http://aspnetwebstack.codeplex.com/workitem/609. Basically, instead of having your Post method's signature take a Resource instance, make it take no parameters and then use JSon.Net (or a new instance of JsonMediaTypeFormatter) to do the deserialization.
public void Post()
{
var json = Request.Content.ReadAsStringAsync().Result;
var resource = Newtonsoft.Json.JsonConvert.DeserializeObject<Resource>(json);
//Important world saving work going on here
}

Resources