When overriding default configuration for Date Serialization, it becomes missing in the JSON example in metadata pages - servicestack

I am attempting to override the default DateTime serialization with the following code:
JsConfig<DateTime>.SerializeFn = d =>
{
return d.ToString("o") + "Z";
};
JsConfig<DateTime>.RawSerializeFn = d =>
{
return d.ToString("o") + "Z";
};
(not sure the diff between SerializeFn and RawSerializeFn so i tried both to be sure...I also tried implementing the DeserializeFn in case they both needed to be overwritten, but saw some results)
Anyways... everytime I try this, any date members in our DTOs goes missing in the sample request/response JSON on the metadata pages. (date members still show in the Parameters section though).
I am using SS v4.0.40.0
PS: I later realized that my whole goal of appending "Z" to all DateTimes could be accomplished with this configuration:
JsConfig.DateHandler = DateHandler.ISO8601;
JsConfig.AssumeUtc = true;
JsConfig.AppendUtcOffset = false;
but I still wanted to file this bug - Thanks!

The DateTime serialization can't be changed in isolation, if you take over serializing to an unsupported custom format you'll also need to handle deserializing it with the appropriate RawDeserializeFn/DeserializeFn configuration.
You can also handle parsing unknown DateTime formats by registering a ParseError callback, i.e:
DateTimeSerializer.OnParseErrorFn = (dateTimeStr, ex) => //DateTime;
If you want to file an issue with any ServiceStack libraries, upgrade to the latest version of ServiceStack to ensure it's still an issue, if it is please submit it to github.com/ServiceStack/Issues with a sample code/failing test that reproduces the issue.

Related

Override JsonDeserializer Behavior

The generator has created a field of type OffsetDateTime:
#Nullable
#ElementName("DocDate")
private OffsetDateTime docDate;
But the server actually returns dates in the format: YYYY-mm-dd i.e. 2021-03-07
When using the generated code I get the following warnings:
WARN - Not deserializable: 2021-03-07
What is the correct way to override the deserialization of these fields? Or have these fields deserialize correctly?
An OffsetDateTime should have both a date and a time. The data your service responds with is lacking the time part. As per the OData V4 ABNF, this is not allowed (assuming your service is a V4 service):
dateTimeOffsetValue = year "-" month "-" day "T" hour ":" minute [ ":" second [ "." fractionalSeconds ] ] ( "Z" / SIGN hour ":" minute )
One way to solve this is to change the property type. You could either:
Change it to Edm.Date in the specification
Or change it to LocalDate in the generated code.
Of course this only makes sense if the service will always respond with a date.
Edit: If you really need to register a custom type adapter (e.g. because the service violates the JSON format) you could override the GsonVdmAdapterFactory:
public <T> TypeAdapter<T> create( #Nonnull final Gson gson, #Nonnull final TypeToken<T> type )
{
if( LocalDate.class.isAssignableFrom(rawType) ) {
return (TypeAdapter<T>) new CustomLocalDateTypeAdapter();
} else {
return super.create(gson, type);
}
}
However, this also requires changing the generated code, because there is currently no convenience to pass a custom type adapter as parameter. Change #JsonAdapter(com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class) to reference your custom factory.
Still, I'd recommend using one of the above workarounds until the service is fixed.

Is there a way to convert a graphql query string into a GraphQLResolveInfo object?

I have written a piece of software that parses and formats the fourth parameter of a graphql resolver function (the info object) to be used elsewhere. I would like to write unit tests for this software. Specifically, I do not want to build the GraphQLResolveInfo object myself, because doing that would be very cumbersome, error-prone and hard to maintain. Instead, I want to write human-readable query strings and convert them to GraphQLResolveInfo objects so I can pass those to my software.
After extensive googling and reading of the graphql-js source code, I have not found a simple way to do what they are doing internally. I'm really hoping that I am missing something.
What I am not trying to do is use the graphql-tag library, because that just generates an AST which has a very different format from the GraphQLResolveInfo type.
Has anyone done this before? Help would be much appreciated!
I will keep monitoring this question to see if a better answer comes along, but I've finally managed to solve my particular issue by creating as close an approximation of the GraphQLResolveInfo object as I need for my particular use case.
The GraphQLResolveInfo object is composed of several attributes, two of which are called fieldNodes and fragments. Both are in fact parts of the same AST that graphql-tag generates from a query string. These are the only parts of the GraphQLResolveInfo object that concern the software I wrote, the rest of it is ignored.
So here is what I did:
import gql from 'graphql-tag';
// The converter function
const convertQueryToResolveInfo = (query) => {
const operation = query.definitions
.find(({ kind }) => kind === 'OperationDefinition');
const fragments = query.definitions
.filter(({ kind }) => kind === 'FragmentDefinition')
.reduce((result, current) => ({
...result,
[current.name.value]: current,
}), {});
return {
fieldNodes: operation.selectionSet.selections,
fragments,
};
};
// An example call
const query = gql`
query {
foo {
bar
}
}
`;
const info = convertQueryToResolveInfo(query);
From the AST generated by graphql-tag, I extract and modify the operation and fragment definitions so that they look the way they do within the GraphQLResolveInfo object. This is by no means perfect and may be subject to change in the future depending on how my software evolves, but it is a relatively brief solution for my particular problem.

Azure Search - Error

When trying to index documents we are getting this error:
{"Token PropertyName in state ArrayStart would result in an invalid JSON object. Path 'value[0]'."}
Our code for indexing using the .NET library is :
using (var indexClient = new SearchIndexClient(searchServiceName, indexName, new SearchCredentials(apiKey)))
{
indexClient.Documents.Index(IndexBatch.Create(IndexAction.Create(documents.Select(doc => IndexAction.Create(doc)))));
}
Does anyone know why this error occurs?
The issue is because of an extra call to IndexAction.Create. If you change your indexing code to this, it will work:
indexClient.Documents.Index(IndexBatch.Create(documents.Select(doc => IndexAction.Create(doc))));
The compiler didn't catch this because IndexBatch.Create has a params argument that can take any number of IndexAction<T> for any type T. In this case, T was a collection, which is not supported (documents must be objects, not collections).
The programming model for creating batches and actions is changing substantially in the 1.0.0-preview release of the SDK. It will be more type-safe so that mistakes like this are more likely to be caught at compile-time.

customized error reporting in v4

Another question on migrating code from v3 to v4:
For v3, I had a customized error reporting, using code like this (in the grammar file):
#members {
public void displayRecognitionError(String[] tokenNames,
RecognitionException e) {
String hdr = getErrorHeader(e);
String msg = getErrorMessage(e, tokenNames);
System.out.println("ERR:"+hdr+":"+msg);
errCount += 1;
}
}
In v4, when compiling the generated java files, I am getting the error:
MyParser.java:163: cannot find symbol
symbol : method getErrorMessage(org.antlr.v4.runtime.RecognitionException,java.lang.String[])
location: class MyParser
String msg = getErrorMessage(e, tokenNames);
^
Is this function replaced by some other function in v4? (I saw some questions and answers on ANTLRErrorListener, but I could not get how to use it for my situation.)
The displayRecognitionError method was removed in ANTLR 4, so even if you correct the body of that method it will not do anything. You need to remove the method from your grammar entirely, and implement ANTLRErrorListener instead. The documentation includes a list of classes that implement the interface, so you can reference those and/or extend one of them to produce the desired functionality.
Once you have an instance of an ANTLRErrorListener, you can use the following code to attach it to a Parser instance.
// remove the default error listener
parser.removeErrorListeners();
// add your custom error listener
parser.addErrorListener(listener);

Extend the default behaviour of AutoMapper

I want to customise the way AutoMapper converts my types without losing the features already implemented by AutoMapper.
I could create a custom ITypeConverter instance but I can't see how to invoke the default behaviour.
Mapper.CreateMap<MyDomainObject, MyDto>
.ConvertUsing<MyTypeConverter>();
...
public class MyTypeConverter : TypeConverter<MyDomainObject, MyDto>
{
public MyDto ConvertCore(MyDomainObject source)
{
var result = // Do the default mapping.
// do my custom logic
return result
}
}
If I try to call var result = Mapper.Map<MyDto>(source) it gets into an infinite loop. I effectively want AutoMapper to do everything it normally would assuming there was no TypeConverter defined.
Any help greatly appreciated.
If you only want to customise some values on the destination object, then you're better off with a Custom Value Resolver - TypeConverters are designed to handle the whole conversion.
The doc page listed above will have enough to get you started: when you have implemented the CustomResolver you apply it like this, and AutoMapper will do the default mapping for the other properties:
Mapper.CreateMap<MyDomainObject, MyDto>()
.ForMember(dest => dest.TargetProperty,
opt => opt.ResolveUsing<CustomResolver>());

Resources