Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing original string of DateTime type token in a custom converter #893

Closed
plokin opened this issue Apr 28, 2016 · 14 comments
Closed

Accessing original string of DateTime type token in a custom converter #893

plokin opened this issue Apr 28, 2016 · 14 comments

Comments

@plokin
Copy link

plokin commented Apr 28, 2016

Same problem as in #694 (default DateParseHandling settings lead to loss of original string value), different solution proposed.

I wanted to write a custom converter to convert my class to/from string (and I need the exact string value even if it looks like datetime). Unfortunately, inside of ReadJson the value is already processed as DateTime, it's too late to set DateParseHandling.None as it cannot be re-read (forward-only) and it is not possible to recover its original string form.
I don't know of any way to configure DateParseHandling.None to be used selectively with my converter only, and not for entire json I'm reading.
I think the easiest way to solve the problem without breaking compatibility would be to provide original string value as a new property of JsonReader (RawValue?) - alongside Path, LinePosition etc.

public override object ReadJson(JsonReader jsonReader, Type type, object obj, JsonSerializer serializer)
{
    jsonReader.DateParseHandling = DateParseHandling.None;
    //   - too late for that - parsing is already done for current token

    return new MyClass(jsonReader.Value.ToString());
    //   - corrupted string for inputs like "2012-01-01T20:44:55+07:00" due to timezone loss

    return new MyClass(JToken.Load(jsonReader).ToString());
    //   - same

    return new MyClass(JRaw.Create(jsonReader).ToString());
    //   - same

    return new MyClass(jsonReader.RawValue);
    //   - this would be awesome
}
@oliverjanik
Copy link

oliverjanik commented Apr 29, 2016

Welcome to the club.

This behaviour is absolutely bonkers. @JamesNK has been closing these issues on the grounds of backwards compatibility, which is understandable. Look's like the only way forward would be a fork.

@JamesNK
Copy link
Owner

JamesNK commented Apr 29, 2016

Why can't you set DateParseHandling on the JsonReader before you start serializing?

@plokin
Copy link
Author

plokin commented Apr 29, 2016

Can I do it once for all deserializations of MyClass? If yes - could you give me some pointer how to achieve that?
I want it to deserialize the same way without having to remember to set DateParseHandling.None every time I do it - I want to reuse this class in many places (for sensitive user strings) without risking damaging the values by mistake.
I need a possibility to tell Json.NET to always use raw string value when deserializing MyClass - after all it's the class (or more specifically its converter) that knows how to ReadJson and I think ability to use original unmodified value belongs to that knowledge internal to the class/converter.

@JamesNK
Copy link
Owner

JamesNK commented Apr 29, 2016

Use JsonConvert.DefaultSettings + DateParseHandling.None

@JamesNK JamesNK closed this as completed Apr 29, 2016
@plokin
Copy link
Author

plokin commented Apr 29, 2016

But how can I tell Json.NET to always use DateParseHandling.None with my converter?
I know I can pass settings to DeserializeObject, but I would need to do it every time I call the method, which is exactly what I don't want to do for reasons explained above. I need some solution to empower my converter to access raw string regardless of settings passed to DeserializeObject.

@asiFarran
Copy link

This is shocking!

@srulandhs
Copy link

For anyone else stumbling on this...

public class MyConverter : JsonConverter<MyType>
{
  : 
        public override MyType ReadJson(JsonReader reader, Type objectType, MyType existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            // Disable converting strings to date objects
            **reader.DateParseHandling = DateParseHandling.None;**
            : 
        }
  : 
}

After this, reader.TokenType will be JToken.String and not JToken.Date. Same with reader.Value.

@erik-neumann
Copy link

For anyone else stumbling on this...

public class MyConverter : JsonConverter<MyType>
{
  : 
        public override MyType ReadJson(JsonReader reader, Type objectType, MyType existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            // Disable converting strings to date objects
            **reader.DateParseHandling = DateParseHandling.None;**
            : 
        }
  : 
}

After this, reader.TokenType will be JToken.String and not JToken.Date. Same with reader.Value.

Having the same problem. Unfortunately the solution above does not seem to work. Setting DateParseHandling seems to be to late and has no effect.

@jdlundquist
Copy link

I know this issue is already closed but wanted to mention I'm running into same issue.
For the overall JSON string, I'd like to use DateParseHandling.DateTime or DateParseHandling.DateTimeOffset. However, for a specific type that handles dates in a custom way, for which I have a custom converter, I'd like to be able to always parse the raw string (skip the automatic date parsing) regardless of the general serialization settings.

The context is ASP.NET Core 3.1 application where I'm using DateTime for properties on the API models that have date and time component and my custom date-related type (contains a date with no time part) for some properties. So, I want to have the automatic date parse handling for the DateTime types but not for my custom type. I have one set of serialization settings configured via services.AddControllers().AddNewtonsoftJson(options => ... ).

@jdlundquist
Copy link

For this particular case, it would be nice to have access within one of the JsonConverter.ReadJson(...) overloads (even if it's a new method to preserve backward compatibility of existing methods) to the original text before date handling logic parsed it. In my case, my custom converter inherits the generic JsonConverter<T>, not JsonConverter.

Specifically for the JsonTextReader, it seems this value is available internally in the private _stringReference but only made available via public JsonReader.Value for the current token when date parse handling is disabled or unsuccessful:

if (_dateParseHandling != DateParseHandling.None)
{
DateParseHandling dateParseHandling;
if (readType == ReadType.ReadAsDateTime)
{
dateParseHandling = DateParseHandling.DateTime;
}
#if HAVE_DATE_TIME_OFFSET
else if (readType == ReadType.ReadAsDateTimeOffset)
{
dateParseHandling = DateParseHandling.DateTimeOffset;
}
#endif
else
{
dateParseHandling = _dateParseHandling;
}
if (dateParseHandling == DateParseHandling.DateTime)
{
if (DateTimeUtils.TryParseDateTime(_stringReference, DateTimeZoneHandling, DateFormatString, Culture, out DateTime dt))
{
SetToken(JsonToken.Date, dt, false);
return;
}
}
#if HAVE_DATE_TIME_OFFSET
else
{
if (DateTimeUtils.TryParseDateTimeOffset(_stringReference, DateFormatString, Culture, out DateTimeOffset dt))
{
SetToken(JsonToken.Date, dt, false);
return;
}
}
#endif
}
SetToken(JsonToken.String, _stringReference.ToString(), false);

I'm not sure what all the ramifications would be for exposing _stringReference through a public property that returns System.String given all the different token types and use cases of token handling. I also realize the base class JsonReader is what's provided to custom converters, e.g. in ReadJson but the reference above is to derived type JsonTextReader.

If it makes sense on JsonReader to expose the raw string that was read to create the token, not just current token JsonReader.Value, that would be ideal. However, even if this only makes sense in the context of JsonTextReader specifically, I could do a check like reader is JsonTextReader inside custom converter.

For reference, here is the top level call to Json.NET from ASP.NET Core for deserializing request body JSON string:
https://github.com/dotnet/aspnetcore/blob/271ebe019823e8e6a751429cd350c2db76ebab05/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs#L178

A totally different approach to achieving this goal, rather than exposing JsonReader._stringReference, could be to allow adjusting the DateParseHandling settings for certain output types from deserialization process. This may be a bit tricky though since the JsonReader parsing of the string happens before, and independently, of any process to convert to output/target type.

@DreamOfTheRedChamber
Copy link

I am facing the same issue

@ernest-morariu
Copy link

I faced a similar issue, I needed the raw value of a field in my custom converter.
I asked them to add the function JsonReader.ReadRawValue, but nobody replied.
However, I sorted it out myself:
You can find the code on stackoverflow here:
Where is JsonReader.ReadRawValue?

@vppetrov
Copy link

Same need here, exactly the use case described by jdlundquist:

I know this issue is already closed but wanted to mention I'm running into same issue. For the overall JSON string, I'd like to use DateParseHandling.DateTime or DateParseHandling.DateTimeOffset. However, for a specific type that handles dates in a custom way, for which I have a custom converter, I'd like to be able to always parse the raw string (skip the automatic date parsing) regardless of the general serialization settings.

The context is ASP.NET Core 3.1 application where I'm using DateTime for properties on the API models that have date and time component and my custom date-related type (contains a date with no time part) for some properties. So, I want to have the automatic date parse handling for the DateTime types but not for my custom type. I have one set of serialization settings configured via services.AddControllers().AddNewtonsoftJson(options => ... ).

@dowdeswells
Copy link

I am having the same problem. Could @JamesNK please tell us the reason why a custom JsonConverter would not be able to access the raw text in the event that the format of that text is known only to that converter. Is it not presumptuous and counter to requirement that some other code converts the raw text to the destination type?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests