GraphQL java send custom error in json format - json

I am working in an graphql application where I have to send custom error object / message in json irrespective of whether it occurs in servlet or service.
Expected error response
{ errorCode: 400 //error goes here,
errorMessage: "my error mesage"}
It will be helpful if someone could guide me to achieve the above requirement.

GraphQL specification defines a clear format for the error entry in the response.
According to the spec, it should like this (assuming JSON format is used):
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {/* You can place data in any format here */}
}
]
So you won't find a GraphQL implementation that allows you to extend it and return some like this in the GraphQL execution result, for example:
"errors": [
{
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
]
However, the spec lets you add data in whatever format in the extension entry. So you could create a custom Exception on the server side and end up with a response that looks like this in JSON:
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
}
]
It's quite easy to implement this on GraphQL Java, as described in the docs. You can create a custom exception that overrides the getExtensions method and create a map inside the implementation that will then be used to build the content of extensions:
public class CustomException extends RuntimeException implements GraphQLError {
private final int errorCode;
public CustomException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
}
#Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new LinkedHashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
#Override
public List<SourceLocation> getLocations() {
return null;
}
#Override
public ErrorType getErrorType() {
return null;
}
}
then you can throw the exception passing in the code and message from inside your data fetchers:
throw new CustomException(400, "A custom error message");
Now, there is another way to tackle this.
Assuming you are working on a Web application, you can return errors (and data, for that matter) in whatever format that you want. Although that is a bit awkward in my opinion. GraphQL clients, like Apollo, adhere to the spec, so why would you want to return a response on any other format? But anyway, there are lots of different requirements out there.
Once you get a hold of an ExecutionResult, you can create a map or object in whatever format you want, serialise that as JSON and return this over HTTP.
Map<String, Object> result = new HashMap<>();
result.put("data", executionResult.getData());
List<Map<String, Object>> errors = executionResult.getErrors()
.stream()
.map(error -> {
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("errorMessage", error.getMessage());
errorMap.put("errorCode", 404); // get the code somehow from the error object
return errorMap;
})
.collect(toList());
result.put("errors", errors);
// Serialize "result" and return that.
But again, having a response that doesn't comply with the spec doesn't make sense in most of the cases.

The other posted answer didn't work for me.
I found a solution by creating the following classes:
1) A throwable CustomException of GraphQLError type (just like mentioned in another answer).
2) Creating a GraphQLError Adaptor, which is not a Throwable.
3) A custom GraphQLErrorHandler to filter the custom exception.
Step 1:
The below throwable CustomGraphQLException implements GraphQLError because the GraphQLErrorHandler interface accepts errors only of type GraphQLError.
public class CustomGraphQLException extends RuntimeException implements GraphQLError {
private final int errorCode;
private final String errorMessage;
public CustomGraphQLException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
#Override
public List<SourceLocation> getLocations() {
return null;
}
#Override
public ErrorType getErrorType() {
return null;
}
#Override
public String getMessage() {
return this.errorMessage;
}
#Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new HashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
}
Step 2:
A non-throwable adaptor of GraphQLError is created to avoid the stack-trace of the above custom exception being passed in the final GraphQL Error Response.
public class GraphQLErrorAdaptor implements GraphQLError {
private final GraphQLError graphQLError;
public GraphQLErrorAdaptor(GraphQLError graphQLError) {
this.graphQLError = graphQLError;
}
#Override
public List<SourceLocation> getLocations() {
return graphQLError.getLocations();
}
#Override
public ErrorType getErrorType() {
return graphQLError.getErrorType();
}
#Override
public String getMessage() {
return graphQLError.getMessage();
}
#Override
public Map<String, Object> getExtensions() {
return graphQLError.getExtensions();
}
}
Step 3:
A custom GraphQLErrorHandler is implemented to filter the custom CustomGraphQLException and avoid its replacement with the default graphQL error response.
public class CustomGraphQLErrorHandler implements GraphQLErrorHandler {
public CustomGraphQLErrorHandler() { }
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
List<GraphQLError> clientErrors = this.filterGraphQLErrors(errors);
List<GraphQLError> internalErrors = errors.stream()
.filter(e -> isInternalError(e))
.map(GraphQLErrorAdaptor::new)
.collect(Collectors.toList());
if (clientErrors.size() + internalErrors.size() < errors.size()) {
clientErrors.add(new GenericGraphQLError("Internal Server Error(s) while executing query"));
errors.stream().filter((error) -> !this.isClientError(error)
).forEach((error) -> {
if (error instanceof Throwable) {
LOG.error("Error executing query!", (Throwable) error);
} else {
LOG.error("Error executing query ({}): {}", error.getClass().getSimpleName(), error.getMessage());
}
});
}
List<GraphQLError> finalErrors = new ArrayList<>();
finalErrors.addAll(clientErrors);
finalErrors.addAll(internalErrors);
return finalErrors;
}
protected List<GraphQLError> filterGraphQLErrors(List<GraphQLError> errors) {
return errors.stream().filter(this::isClientError).collect(Collectors.toList());
}
protected boolean isClientError(GraphQLError error) {
return !(error instanceof ExceptionWhileDataFetching) && !(error instanceof Throwable);
}
protected boolean isInternalError(GraphQLError error) {
return (error instanceof ExceptionWhileDataFetching) &&
(((ExceptionWhileDataFetching) error).getException() instanceof CustomGraphQLException);
}
}
Step 4:
Configure the CustomGraphQLErrorHandler in GraphQLServlet. I am assuming you are using spring-boot for this step.
#Configuration
public class GraphQLConfig {
#Bean
public ServletRegistrationBean graphQLServletRegistrationBean(
QueryResolver queryResolver,
CustomGraphQLErrorHandler customGraphQLErrorHandler) throws Exception {
GraphQLSchema schema = SchemaParser.newParser()
.schemaString(IOUtils.resourceToString("/library.graphqls", Charset.forName("UTF-8")))
.resolvers(queryResolver)
.build()
.makeExecutableSchema();
return new ServletRegistrationBean(new SimpleGraphQLServlet(schema,
new DefaultExecutionStrategyProvider(), null, null, null,
customGraphQLErrorHandler, new DefaultGraphQLContextBuilder(), null,
null), "/graphql");
}
}
Reference

Related

getting error : Data provider mismatch Method: loginTest

Please help me to resolve this issue. I am trying to extract data from Json using data provider for one of my test case. I have done the below code.
This below method is in base class :
public List<HashMap<String, String>> getJsonDataToMap(String filePath) throws IOException {
String jsonContent = FileUtils.readFileToString(new File(filePath), StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper();
List<HashMap<String, String>> data = mapper.readValue(jsonContent, new TypeReference<List<HashMap<String, String>>>() {
});
return data;
}
Now Login.Test class is extending Base class and i have created these methods to extract the data from json and use in in test case :
public class LoginTest extends BaseClass {
public static String dataPath="src/test/java/com/automationqa/testData/LoginData.json";
#DataProvider(name = "testdatalogin")
public Object[][] getData() throws IOException {
List<HashMap<String, String>> data = getJsonDataToMap(dataPath);
return new Object[][] {{data.get(0), data.get(1)}};
}
#Test(dataProvider = "testdatalogin")
public void loginTest(HashMap<String, String> input) {
loginPage.enterUserName(input.get("userid"));
loginPage.enterPassword(input.get("password"));
loginPage.clickOnLogin();
String pageTitle = driver.getTitle();
Assertions.assertThat(pageTitle).isEqualTo("Guru99 Bank Manager HomePage");
}
JSon file :
[
{
"userid": "mngr452488",
"password": "davysav"
},
{
"userid": "mgr45248xyz",
"password": "heavyset"
}
]
but getting below error:
org.testng.internal.reflect.MethodMatcherException:
Data provider mismatch
Method: loginTest([Parameter{index=0, type=java.util.HashMap, declaredAnnotations=[]}])
Arguments: [(java.util.HashMap) {password=davysav, userid=mngr452488},(java.util.HashMap) {password=heavyset, userid=mgr45248xyz}]
at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:45)
at org.testng.internal.Parameters.injectParameters(Parameters.java:796)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:983)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
at org.testng.SuiteRunner.run(SuiteRunner.java:364)
JSon file :
[
{
"userid": "mngr452488",
"password": "davysav"
},
{
"userid": "mgr45248xyz",
"password": "heavyset"
}
]
but getting below error:
org.testng.internal.reflect.MethodMatcherException:
Data provider mismatch
Method: loginTest([Parameter{index=0, type=java.util.HashMap, declaredAnnotations=[]}])
Arguments: [(java.util.HashMap) {password=davysav, userid=mngr452488},(java.util.HashMap) {password=heavyset, userid=mgr45248xyz}]
at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:45)
at org.testng.internal.Parameters.injectParameters(Parameters.java:796)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:983)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
at org.testng.SuiteRunner.run(SuiteRunner.java:364)
Your provider provides two arguments for each iteration but test method takes only one.
I think you want to do something like
#DataProvider(name = "testdatalogin")
public Object[][] getData() throws IOException {
List<Map<String, String>> data = getJsonDataToMap(dataPath);
return new Object[][] {{data.get(0)}, {data.get(1)}};
}

Spring WebFlux - Add a wrapping class before serialization

I'm developing APIs for an exam project, but I wanted their responses to be consistently using a wrapping class on all of them (Telegram Bot API style for those who know them).
So, for example, having these two classes:
public class User {
public int id;
public String name;
}
public class Item {
public int id;
public String itemName;
public User owner;
}
What Spring returns to me is this output:
{
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
What I want instead is for this output to be returned:
{
"ok": true,
"data": {
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
}
Maybe using a class wrapper like this:
public class ResponseWrapper<T> {
public boolean ok;
public T data;
}
Is it possible to do this?
I understand you need a global setting to convert all your responses into a standard one. For this you can implement ResponseBodyAdvice and have a common structure for all your api responses. Refer this link for a detailed example
Edit: For spring-webflux you can extend ResponseBodyResultHandler and override handleResult. An example is given in this answer
I thank #JustinMathew for the help, at the end, in my case (using Spring WebFlux with Kotlin), the ResponseBodyResultHandler class was more useful to me.
// File: /MicroserviceApplication.kt
#SpringBootApplication
class MicroserviceApplication {
#Autowired
lateinit var serverCodecConfigurer: ServerCodecConfigurer
#Autowired
lateinit var requestedContentTypeResolver: RequestedContentTypeResolver
#Bean
fun responseWrapper(): ResponseWrapper = ResponseWrapper(
serverCodecConfigurer.writers, requestedContentTypeResolver
)
}
// File: /wrapper/model/Response.kt
data class Response<T>(
val ok: Boolean,
val data: T?,
val error: Error? = null
) {
data class Error(
val value: HttpStatus,
val message: String?
)
}
// File: /wrapper/ResponseWrapper.kt
class ResponseWrapper(writers: List<HttpMessageWriter<*>>, resolver: RequestedContentTypeResolver) :
ResponseBodyResultHandler(writers, resolver) {
override fun supports(result: HandlerResult): Boolean =
(result.returnType.resolve() == Mono::class.java)
|| (result.returnType.resolve() == Flux::class.java)
#Throws(ClassCastException::class)
override fun handleResult(exchange: ServerWebExchange, result: HandlerResult): Mono<Void> {
val body = when (val value = result.returnValue) {
is Mono<*> -> value
is Flux<*> -> value.collectList()
else -> throw ClassCastException("The \"body\" should be Mono<*> or Flux<*>!")
}
.map { r -> Response(true, r, null) }
.onErrorMap { e ->
if (e !is Response.Error)
Response.Error(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error")
else e
}
.onErrorResume { e -> Mono.just(Response(false, null, e as Response.Error)) }
return writeBody(body, returnType, exchange)
}
companion object {
#JvmStatic
private fun methodForReturnType(): Mono<Response<Any>>? = null
private val returnType: MethodParameter = MethodParameter(
ResponseWrapper::class.java.getDeclaredMethod("methodForReturnType"), -1
)
}
Edit: I made of this answer a library for Spring WebFlux 2.7.3 here.
P.S. I also took a cue from this other question, which faces the same problem but with Java.

Reading Very Complex JSON using Spring Batch

My objective is to read a very complex JSON using Spring Batch. Below is the sample JSON.
{
"order-info" : {
"order-number" : "Test-Order-1"
"order-items" : [
{
"item-id" : "4144769310"
"categories" : [
"ABCD",
"DEF"
],
"item_imag" : "http:// "
"attributes: {
"color" : "red"
},
"dimensions" : {
},
"vendor" : "abcd",
},
{
"item-id" : "88888",
"categories" : [
"ABCD",
"DEF"
],
.......
I understand that I would need to create a Custom ItemReader to parse this JSON.
Kindly provide me some pointers. I am really clueless.
I am now not using CustomItemReader. I am using Java POJOs. My JsonItemReader is as per below:
#Bean
public JsonItemReader<Trade> jsonItemReader() {
ObjectMapper objectMapper = new ObjectMapper();
JacksonJsonObjectReader<Trade> jsonObjectReader =
new JacksonJsonObjectReader<>(Trade.class);
jsonObjectReader.setMapper(objectMapper);
return new JsonItemReaderBuilder<Trade>()
.jsonObjectReader(jsonObjectReader)
.resource(new ClassPathResource("search_data_1.json"))
.name("tradeJsonItemReader")
.build();
}
The exception which I now get is :
java.lang.IllegalStateException: The Json input stream must start with an array of Json objects
From similar posts in this forum I understand that I need to use JsonObjectReader. "You can implement it to read a single json object and use it with the JsonItemReader (either at construction time or using the setter)".
How can I do this either # construction time or using setter? Please share some code snippet for the same.
The delegate of MultiResourceItemReader should still be a JsonItemReader. You just need to use a custom JsonObjectReader with the JsonItemReader instead of JacksonJsonObjectReader. Visually, this would be: MultiResourceItemReader -- delegates to --> JsonItemReader -- uses --> your custom JsonObjectReader.
Could you please share a code snippet for the above?
JacksonJsonItemReader is meant to parse from a root node that is already and array node, so it expects your json to start with '['.
If you desire to parse a complex object - in this case, one that have many parent nodes/properties before it gets to the array - you should write a reader. It is really simple to do it and you can follow JacksonJsonObjectReader's structure. Here follows and example of a generic reader for complex object with respective unit tests.
The unit test
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.springframework.core.io.ByteArrayResource;
import com.example.batch_experiment.dataset.Dataset;
import com.example.batch_experiment.dataset.GenericJsonObjectReader;
import com.example.batch_experiment.json.InvalidArrayNodeException;
import com.example.batch_experiment.json.UnreachableNodeException;
import com.fasterxml.jackson.databind.ObjectMapper;
#RunWith(BlockJUnit4ClassRunner.class)
public class GenericJsonObjectReaderTest {
GenericJsonObjectReader<Dataset> reader;
#Before
public void setUp() {
reader = new GenericJsonObjectReader<Dataset>(Dataset.class, "results");
}
#Test
public void shouldRead_ResultAsRootNode() throws Exception {
reader.open(new ByteArrayResource("{\"result\":{\"results\":[{\"id\":\"a\"}]}}".getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
}
#Test
public void shouldIgnoreUnknownProperty() throws Exception {
String jsonStr = "{\"result\":{\"results\":[{\"id\":\"a\", \"aDifferrentProperty\":0}]}}";
reader.open(new ByteArrayResource(jsonStr.getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
}
#Test
public void shouldIgnoreNullWithoutQuotes() throws Exception {
String jsonStr = "{\"result\":{\"results\":[{\"id\":\"a\",\"name\":null}]}}";
try {
reader.open(new ByteArrayResource(jsonStr.getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
#Test
public void shouldThrowException_OnNullNode() throws Exception {
boolean exceptionThrown = false;
try {
reader.open(new ByteArrayResource("{}".getBytes()) {});
} catch (UnreachableNodeException e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
}
#Test
public void shouldThrowException_OnNotArrayNode() throws Exception {
boolean exceptionThrown = false;
try {
reader.open(new ByteArrayResource("{\"result\":{\"results\":{}}}".getBytes()) {});
} catch (InvalidArrayNodeException e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
}
#Test
public void shouldReadObjectValue() {
try {
reader.setJsonParser(new ObjectMapper().createParser("{\"id\":\"a\"}"));
Dataset dataset = reader.read();
Assert.assertNotNull(dataset);
Assert.assertEquals("a", dataset.getId());
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
}
And the reader:
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.json.JsonObjectReader;
import org.springframework.core.io.Resource;
import com.example.batch_experiment.json.InvalidArrayNodeException;
import com.example.batch_experiment.json.UnreachableNodeException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
/*
* This class follows the structure and functions similar to JacksonJsonObjectReader, with
* the difference that it expects a object as root node, instead of an array.
*/
public class GenericJsonObjectReader<T> implements JsonObjectReader<T>{
Logger logger = Logger.getLogger(GenericJsonObjectReader.class.getName());
ObjectMapper mapper = new ObjectMapper();
private JsonParser jsonParser;
private InputStream inputStream;
private ArrayNode targetNode;
private Class<T> targetType;
private String targetPath;
public GenericJsonObjectReader(Class<T> targetType, String targetPath) {
super();
this.targetType = targetType;
this.targetPath = targetPath;
}
public JsonParser getJsonParser() {
return jsonParser;
}
public void setJsonParser(JsonParser jsonParser) {
this.jsonParser = jsonParser;
}
public ArrayNode getDatasetNode() {
return targetNode;
}
/*
* JsonObjectReader interface has an empty default method and must be implemented in this case to set
* the mapper and the parser
*/
#Override
public void open(Resource resource) throws Exception {
logger.info("Opening json object reader");
this.inputStream = resource.getInputStream();
JsonNode jsonNode = this.mapper.readTree(this.inputStream).findPath(targetPath);
if (!jsonNode.isMissingNode()) {
this.jsonParser = startArrayParser(jsonNode);
logger.info("Reader open with parser reference: " + this.jsonParser);
this.targetNode = (ArrayNode) jsonNode; // for testing purposes
} else {
logger.severe("Couldn't read target node " + this.targetPath);
throw new UnreachableNodeException();
}
}
#Override
public T read() throws Exception {
try {
if (this.jsonParser.nextToken() == JsonToken.START_OBJECT) {
T result = this.mapper.readValue(this.jsonParser, this.targetType);
logger.info("Object read: " + result.hashCode());
return result;
}
} catch (IOException e) {
throw new ParseException("Unable to read next JSON object", e);
}
return null;
}
/**
* Creates a new parser from an array node
*/
private JsonParser startArrayParser(JsonNode jsonArrayNode) throws IOException {
JsonParser jsonParser = this.mapper.getFactory().createParser(jsonArrayNode.toString());
if (jsonParser.nextToken() == JsonToken.START_ARRAY) {
return jsonParser;
} else {
throw new InvalidArrayNodeException();
}
}
#Override
public void close() throws Exception {
this.inputStream.close();
this.jsonParser.close();
}
}

Spring Rest Post Request Enumeration property

I am using Spring version 4.2 with Spring boot. I have a post request
http://localhost:3000/api/standards/
for which I have the following json request body
{
"standardName": "MyStandard",
"language": "Java",
}
All I want is to save a Standard entity. The 'language' property of the StandardEntity is of type Enum.
My Controller method looks like this
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Standard> createStandard(#Validated #RequestBody Standard standard ) {
standardService.createStandard(standard);
return new ResponseEntity<Standard>(standard, HttpStatus.CREATED);
}
But inside the controller the standard.getLangauge() is always null.
I have tried the #InitBinder and adding #JsonCreator to the Language enum but none of them works for me.
My Enum looks like this
public enum Language {
#JsonProperty("java")Java("java"),
#JsonProperty("sql")Sql("sql");
private String value;
private Language(String value) {
this.value = value;
}
public static Language fromValue(String value) {
for (Language language : values()) {
if (language.value.equalsIgnoreCase(value)) {
return language;
}
}
throw new IllegalArgumentException(
"Unknown enum type " + value + ", Allowed values are " + Arrays.toString(values()));
}
#Override
public String toString() {
return value;
}
#JsonCreator
public static Language create(String value) {
System.out.println("in json creator "+value);
if (value == null) {
throw new IllegalArgumentException();
}
for (Language v : values()) {
if (value.equals(v.getShortName())) {
return v;
}
}
throw new IllegalArgumentException();
}
Your request should be "java" instead of "Java"
{
"standardName": "MyStandard",
"language": "java"
}
When you are sending "Java" it in not able to map it to any Enum, so ideally it should throw an exception of type HttpMessageNotReadableException, check the stack trace there should be such exception.

Force WCF Rest client to use Json deserializer regardless of content-type

How to force WCF Rest client to use Json deserializer regardless of content-type?
I am invoking a REST based web service through WCF.
The service returns JSON body, but has content-type "Application/xml". The WCF framework is now giving me the XmlException.
public class MessageFormatter : IClientMessageFormatter
{
private readonly IClientMessageFormatter _formatter;
public MessageFormatter(IClientMessageFormatter formatter)
{
_formatter = formatter;
}
public object DeserializeReply(System.ServiceModel.Channels.Message message, object[] parameters)
{
return _formatter.DeserializeReply(message, parameters);
}
}
that _formatter.DeserializeReply is throwing XmlException. I can't find any example anywhere to force json deserialization on reply.
Edit - The "message" object when moused over is throwing "{... Error reading body: System.Xml.XmlException: The data at the root level is invalid. Line 1, position 1. ...}"
That same object in another one of my project that communicate with a different REST service (Picasa web services) has a what seems like a xml serialised version of JSON object?? So the problem seems further up the stream. I need to find where this object is originating from. I'll go play around with MessageEncoder class.
Edit - (Adding more info)
public class MyBinding : WebHttpBinding
{
public MyBinding(WebHttpSecurityMode mode)
: base(mode)
{
}
public override BindingElementCollection CreateBindingElements()
{
var result = base.CreateBindingElements();
var replacements = result.OfType<MessageEncodingBindingElement>().ToList();
foreach (var messageEncodingBindingElement in replacements)
{
var index = result.IndexOf(messageEncodingBindingElement);
result.Remove(messageEncodingBindingElement);
result.Insert(index, new MyMessageEncodingBindingElement(messageEncodingBindingElement));
}
return result;
}
}
public class MyMessageEncodingBindingElement : MessageEncodingBindingElement
{
private readonly MessageEncodingBindingElement _element;
public MyMessageEncodingBindingElement(MessageEncodingBindingElement element)
{
_element = element;
}
public override BindingElement Clone()
{
var result = _element.Clone();
if (result is MessageEncodingBindingElement)
return new MyMessageEncodingBindingElement(result as MessageEncodingBindingElement);
return result;
}
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new MyMessageEncoderFactory(_element.CreateMessageEncoderFactory());
}
}
The method CreateMessageEncoderFactory() is never called even when the constructor and Clone method are hit when breakpoints are set. Any help? I'm trying to set a custom MessageEncoder and MessageEncoderFactory class to modify the instantiation process of the Message object.
You can use a WebContentTypeMapper for that. That's a property of the WebHttpBinding, and you can customize how the deserialization will be done by the encoder from that binding, including forcing it to always use the JSON deserializer, regardless of the incoming message's Content-Type. The code below shows how this can be done.
public class StackOverflow_13225272
{
[DataContract]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
public override string ToString()
{
return string.Format("Person[Name={0},Age={1}]", Name, Age);
}
}
[ServiceContract]
public interface ITest
{
[WebGet(ResponseFormat = WebMessageFormat.Json)]
Person GetPerson(string responseContentType);
}
public class Service : ITest
{
public Person GetPerson(string responseContentType)
{
WebOperationContext.Current.OutgoingResponse.ContentType = responseContentType;
return new Person { Name = "John Doe", Age = 29 };
}
}
class AllJsonContentTypeMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Json;
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
#if USE_NETFX4
// This works on .NET 4.0 and beyond
WebHttpBinding binding = new WebHttpBinding();
binding.ContentTypeMapper = new AllJsonContentTypeMapper();
#else
// This works on .NET 3.5
CustomBinding binding = new CustomBinding(new WebHttpBinding());
binding.Elements.Find<WebMessageEncodingBindingElement>().ContentTypeMapper = new AllJsonContentTypeMapper();
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress));
#endif
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest proxy = factory.CreateChannel();
Console.WriteLine("With JSON: {0}", proxy.GetPerson("application/json"));
Console.WriteLine("With XML: {0}", proxy.GetPerson("application/xml"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
This might work.
public class ForceJsonClientMessageFormatter : IClientMessageFormatter
{
private readonly DataContractJsonSerializer _jsonSerializer;
public ForceJsonClientMessageFormatter(Type responseType)
{
_jsonSerializer = new DataContractJsonSerializer(responseType);
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
throw new NotImplementedException("This client message formatter is for replies only!");
}
public object DeserializeReply(Message message, object[] parameters)
{
string messageBody = message.GetBody<string>();
using (MemoryStream messageStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)))
{
messageStream.Seek(0, SeekOrigin.Begin);
object deserializedObject = _jsonSerializer.ReadObject(messageStream);
return deserializedObject;
}
}
}
public class ForceJsonWebHttpBehavior : WebHttpBehavior
{
protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
return new ForceJsonClientMessageFormatter(operationDescription.Messages[1].Body.ReturnValue.Type);
}
}
I haven't tried it, but I think this will work. You can create a custom IClientMessageFormatter which overwrites the message format to Json, wrap that in a behavior, and then apply that behavior to your client endpoint configuration.
public class ForceJsonClientMessageFormatterDecorator : IClientMessageFormatter
{
private readonly IClientMessageFormatter _decoratedFormatter;
public ForceJsonClientMessageFormatterDecorator(IClientMessageFormatter decoratedFormatter)
{
_decoratedFormatter = decoratedFormatter;
}
public object DeserializeReply(Message message, object[] parameters)
{
message.Properties[WebBodyFormatMessageProperty.Name] = new WebBodyFormatMessageProperty(WebContentFormat.Json);
return _decoratedFormatter.DeserializeReply(message, parameters);
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
return _decoratedFormatter.SerializeRequest(messageVersion, parameters);
}
}
public class ForceJsonWebHttpBehavior : WebHttpBehavior
{
protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
IClientMessageFormatter decoratedFormatter = base.GetReplyClientFormatter(operationDescription, endpoint);
return new ForceJsonClientMessageFormatterDecorator(decoratedFormatter);
}
}