Newtonsoft JSON - custom type?



  • Our company has a custom type, "Currency", which is more or less a wrapper around Decimal, but with lots of goodies in it. We also have a static function ParseOrDefault( string s ) which will take a string and parse it into a nullable Currency.

    So here's my question: how do I tell Newtonsoft JSON that if it encounters a Currency? type it should use that static function to parse it?



  • @blakeyrat I'm not clear on which way you are trying to go; JSON to .NET Object, .NET Object to JSON or Stringy-.NET Object to JSON?


  • Winner of the 2016 Presidential Election

    I definitely have example code I could anonymize and share, at work. Unfortunately, you posted this after hours. If it's still unanswered tomorrow I will share.

    Basically it involves writing a special class and decorating your custom type with an attribute telling it to use that class.



  • @MathNerdCNU The actual bug I'm trying to fix is JSON -> .net object. It already serializes the other direction correctly, simply due to Currency implementing .ToString() I believe.


  • Impossible Mission Players - A

    Not sure, but so long as the object's properties aren't read-only, it should serialize them OK?

    Some quick Googs:



  • @blakeyrat I think something like this may work but it sounds like @error has more insight and a better solution for ya.


  • Impossible Mission Players - A

    @blakeyrat said in Newtonsoft JSON - custom type?:

    It already serializes the other direction correctly, simply due to Currency implementing .ToString() I believe.

    Can it be assigned a string? Or is ParseOrDefault (string blah) the only input like that?



  • @Tsaukpaetra But I'm serializing from a string. Nobody knows how to turn "3764.2352" into "Currency".

    Now Currency has a member, which is a Decimal, and stuff knows how to turn "3764.2352" into a Decimal, but the Decimal is all private and tucked-away.

    I don't blame Newtonsoft for not telepathically knowing how to serialize this type, but man trying to find an example of how to do it is exhausting. Your second link looks promising maybe.


  • Winner of the 2016 Presidential Election

    @MathNerdCNU said in Newtonsoft JSON - custom type?:

    @blakeyrat I think something like this may work but it sounds like @error has more insight and a better solution for ya.

    Actually that article is explaining the exact approach I was poorly describing.

    It's a little more complicated than I can write out by rote, but seeing the code I can dissect it and explain it.



  • @error I think I get the idea from that example. And our code probably already has a couple JsonConverters sitting around, but I'm dealing with a test failure right now.


  • Winner of the 2016 Presidential Election

    Actually, I'm not sure why the article is using JObject.Load. IIRC the JsonReader and JsonSerializer objects injected in the method are sufficient to retrieve an intermediate representation of the object during parsing for you to convert to your custom type.

    Edit: My understanding is that the JsonReader reads tokens from the stream individually, and the JsonSerializer can take the stream of tokens from JsonReader and read more complex objects from it.

    Editedit: OK, I guess JObject gives you a dynamic object so you don't have to make a container class for your intermediary type or read out properties individually. It's probably overkill for this use-case, but convenient if you're deserializing a complex object.



  • The way we did something like this was to implement a JsonConverter. This isn't exactly our code, but mangled to something similar to what you would need (and not tested if it even works or compiles).

    The trick is in the ReadJson bit - this implementation also checks if the incoming token is a number instead of a string.

    public class CurrencyConverter : JsonConverter
    {
        public override bool CanConvert(System.Type objectType)
        {
            return objectType == typeof(Currency) || objectType == typeof(Currency?);	// Assuming it's a struct
        }
    
        public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType != JsonToken.Null)
            {
                if (reader.TokenType == JsonToken.String
                    || reader.TokenType == JsonToken.Integer
                    || reader.TokenType == JsonToken.Float)
                {
                    try
                    {
                        var value = Convert.ToDecimal(reader.Value, CultureInfo.InvariantCulture);
    
    					return new Currency(value);
                    }
                    catch (Exception ex)
                    {
                        throw new JsonSerializationException(string.Format("Error converting value '{0}' to Currency.", reader.Value), ex);
                    }
                }
    
                throw new JsonSerializationException(string.Format("Unexpected token when parsing Currency. Got {0}.", reader.TokenType));
            }
    
            if (objectType != typeof(Currency?))
            {
                throw new JsonSerializationException(string.Format("Cannot convert null value to Currency"));
            }
    
            return null;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value == null)
            {
                writer.WriteNull();
            }
            else
            {
                writer.WriteValue(value.ToString());
            }
        }
    }
    

    In the webapi config, we then registered the converter with a single line:

    config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new CurrencyConverter());



  • @Quinnum Thanks.

    I actually got involved in a build break and did that until quitting time, but I'll get back at it tomorrow morning.


  • Winner of the 2016 Presidential Election

    @blakeyrat Did you find the answers you needed or do you still need help?



  • @error I dunno, it's been meetings all day today.



  • It turns out when I look we already have a JsonConverter for the Currency class, which already supports(?) nullable Currency. So. Something else weird is going on here.

    Will update when I get more information.

    EDIT: derp. It's used in the WebAPI, but doesn't appear to ever be added to the SerializerSettings of this CLI tool. That's nearly 100% certain to be the bug.


  • Impossible Mission - B

    @blakeyrat said in Newtonsoft JSON - custom type?:

    It turns out when I look we already have a JsonConverter for the Currency class, which already supports(?) nullable Currency. So. Something else weird is going on here.

    Will update when I get more information.

    Put a breakpoint in the relevant method and see if it's ever getting called.


  • Impossible Mission Players - A

    @masonwheeler said in Newtonsoft JSON - custom type?:

    Put a breakpoint in the relevant method and see if it's ever getting called.

    Wouldn't help if it's flat-out not included in the version he's working in though, huh?


  • Impossible Mission - B

    @Tsaukpaetra said in Newtonsoft JSON - custom type?:

    Wouldn't help if it's flat-out not included in the version he's working in though, huh?

    In that case, it would never get called. This tells you something useful.



  • @masonwheeler I know it's not, the CLI tool for some strange reason doesn't run the WebApiConfig.cs class in our REST API, go figure.

    Anyway, this is all fixed now. My question was a stupid question, the development work had already been done and I was just missing a bit of configuration.


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.