Roland Deschain
Roland Deschain

Reputation: 2830

How to serialize class to JSON, where property is of type Windows.Data.Json.JsonObject?

Consider the following class:

public class ImageDataModel
{
    public JsonObject CaptureResultData { get; }
    public SmartphoneCommand SmartphoneCommand { get; }
    public LightStageCommand LightStageCommand { get; }

    public string TimeStamp { get; }

    public ImageDataModel(string _captureResultData, LightStageCommand _lightStageCommand, SmartphoneCommand _smartphoneCommand)
    {
        CaptureResultData = JsonObject.Parse(_captureResultData);
        SmartphoneCommand = _smartphoneCommand;
        LightStageCommand = _lightStageCommand;
        TimeStamp = DateTime.Now.ToString("HH:mm.ss, dd. MM. yyyy");
    }
}

SmartphoneCommandand LightStageCommand are serializable objects, no problem with those. However, in the constructor, _captureResultData is already a serialized JSON string of type JsonObject. Since I want the data in the this string to show up as serialized object data instead of a single string within my JSON file, I made it into a JsonObject.

The problem is, after serialization, the CaptureResult data shows up in the JSON file as follows:

  "CaptureResultData": {
    "android.control.afMode": {
      "ValueType": 2
    },
    "android.colorCorrection.gains": {
      "ValueType": 3
    },
    "android.control.awbMode": {
      "ValueType": 2
    },
    "android.lens.focalLength": {
      "ValueType": 2
    },
    "android.lens.focusDistance": {
      "ValueType": 2
    },
    "android.control.aeMode": {
      "ValueType": 2
    },
    "android.colorCorrection.mode": {
      "ValueType": 2
    },
    "android.colorCorrection.transform": {
      "ValueType": 3
    },
    "android.lens.aperture": {
      "ValueType": 2
    },
    "android.sensor.sensitivity": {
      "ValueType": 2
    },
    "android.sensor.exposureTime": {
      "ValueType": 2
    }
  },

The original string contains the correct data. How can I force the serialization to show the actual data, instead of ValueType?

For completeness, here is how the serialization is done:

using (StreamWriter jsonFile = File.CreateText(uniqueFilePaths[2]))
{
    JsonSerializer serializer = new JsonSerializer
    {
        Formatting = Formatting.Indented,
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
    };

    serializer.Serialize(jsonFile, new ImageDataModel(captureResultString, lightStageCommand, cameraCommand));
}

Upvotes: 1

Views: 242

Answers (1)

dbc
dbc

Reputation: 116669

Json.NET does not have any support for the types in the Windows.Data.Json namespace, so you will need to create a custom JsonConverter for JsonObject as well as JsonArray and JsonValue if you ever use those classes directly. The following should do the job:

public class WindowsDataJsonObjectConverter : WindowsDataJsonConverterBase<Windows.Data.Json.JsonObject>
{
    public override JsonObject ReadJson(JsonReader reader, Type objectType, JsonObject existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        JsonObject.Parse(reader.ReadOuterJson(dateParseHandling: DateParseHandling.None));
}

public class WindowsDataJsonArrayConverter : WindowsDataJsonConverterBase<Windows.Data.Json.JsonArray>
{
    public override JsonArray ReadJson(JsonReader reader, Type objectType, JsonArray existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        JsonArray.Parse(reader.ReadOuterJson(dateParseHandling: DateParseHandling.None));
}

public class WindowsDataJsonValueConverter : WindowsDataJsonConverterBase<Windows.Data.Json.JsonValue>
{
    public override JsonValue ReadJson(JsonReader reader, Type objectType, JsonValue existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        JsonValue.Parse(reader.ReadOuterJson(dateParseHandling: DateParseHandling.None));
}

public abstract class WindowsDataJsonConverterBase<TJsonValue> : JsonConverter<TJsonValue> where TJsonValue : IJsonValue
{
    public override void WriteJson(JsonWriter writer, TJsonValue value, JsonSerializer serializer) => 
        writer.WriteRawValue(value.Stringify());
}

public static partial class JsonExtensions
{
    // Taken from this answer https://stackoverflow.com/a/56945050/3744182
    // To https://stackoverflow.com/questions/56944160/efficiently-get-full-json-string-in-jsonconverter-readjson
    public static string ReadOuterJson(this JsonReader reader, Formatting formatting = Formatting.None, DateParseHandling? dateParseHandling = null, FloatParseHandling? floatParseHandling = null)
    {
        var oldDateParseHandling = reader.DateParseHandling;
        var oldFloatParseHandling = reader.FloatParseHandling;
        try
        {
            if (dateParseHandling != null)
                reader.DateParseHandling = dateParseHandling.Value;
            if (floatParseHandling != null)
                reader.FloatParseHandling = floatParseHandling.Value;
            using (var sw = new StringWriter(CultureInfo.InvariantCulture))
            using (var jsonWriter = new JsonTextWriter(sw) { Formatting = formatting })
            {
                jsonWriter.WriteToken(reader);
                return sw.ToString();
            }
        }
        finally
        {
            reader.DateParseHandling = oldDateParseHandling;
            reader.FloatParseHandling = oldFloatParseHandling;
        }
    }
}

Notes:

  • JsonObject implements IDictionary<String,IJsonValue> and JsonArray implements several IEnumerable interfaces, so Json.NET will know how to serialize these types. it will not, however, know to deserialize them by calling the appropriate static Parse() methods.

  • There is no standard interface to indicate that a c# type should be serialized to JSON using its "raw" ToString() value, so Json.NET has no way to know how to serialize or deserialize JsonValue.

  • Alternatively, you could consider replacing JsonObject with Json.NET's JObject in your model. If you do, Json.NET will be able to serialize it without any conversion.

    As a second alternative, you could leave _captureResultData as a string in your data model, and mark the property with [JsonConverter(typeof(RawConverter))] where RawConverter comes from this answer to How can I serialize and deserialize a type with a string member that contains "raw" JSON, without escaping the JSON in the process.

  • Since ImageDataModel is immutable, if you want to deserialize it with Json.NET you will need to create a compatible constructor and mark it with JsonConstructorAttribute:

     [JsonConstructor]
     ImageDataModel(JsonObject captureResultData, LightStageCommand lightStageCommand, SmartphoneCommand smartphoneCommand, string timeStamp)
     {
         this.CaptureResultData = captureResultData;
         this.SmartphoneCommand = smartphoneCommand;
         this.LightStageCommand = lightStageCommand;
         this.TimeStamp = timeStamp;
     }
    

    Json.NET will match the JSON properties to constructor arguments using a case-invariant name match.

Mockup fiddle here.

Upvotes: 1

Related Questions