Manatee.Json can do direct JSON-JSON transforms without the mess of special serialization converters. It's a target-first approach that uses JSONPath to identify the specific elements within the source data.
For reference, your source data:
[{"Key1":87,"Key2":99},{"Key1":42,"Key2":-8}]
We then define a template:
[
["Key1","Key2"],
["$[*]","$.Key1"],
["$[*]","$.Key2"]
]
This will map your source data to:
[["Key1","Key2"],[87,42],[99,-8]]
just as you wanted.
The template is based on jsonpath-object-transform. Here's how it works:
- For the most part, the template has the same shape as your target.
- For each property, you specify a JSON Path that identifies the data in the source. (Object property mapping isn't directly shown in this example since you only have arrays, but the link above has a few.)
- There is a special case for arrays. If the array has two elements, and the first element is a JSON Path, then the second array is interpreted as the template for each item in the array. Otherwise, the array is copied as is, mapping data from the source as normal when the element is a path.
So for your case (forgive the C-style comments in the JSON),
[ // Root is an array.
["Key1","Key2"], // Array literal.
["$[*]","$.Key1"], // Take all of the elements in the original array '$[*]'
// and use the value under the "Key1" property '$.Key1'
["$[*]","$.Key2"] // Similiarly for the "Key2" property
]
Note There is an edge case where you want to map a value to a literal array with two elements. This will not work properly.
Once mapped, you can deserialize however you like (Manatee.Json can do that for you, too.).
Edit
I realized that I didn't put any code in my answer, so here it is.
JsonValue source = new JsonArray
{
new JsonObject {{"Key1", 87}, {"Key2", 99}},
new JsonObject {{"Key1", 42}, {"Key2", -8}}
};
JsonValue template = new JsonArray
{
new JsonArray {"Key1", "Key2"},
new JsonArray {"$[*]", "$.Key1"},
new JsonArray {"$[*]", "$.Key2"}
};
var result = source.Transform(template);
That's it.
Edit 2
I was having trouble devising a reverse translation, so here's how you would do this with serialization only.
You would need to register a couple methods to perform the mapping and serialization yourself. Essentially, you instruct the serializer how to build and descontruct the JSON.
Your data model:
public class MyData
{
public int Key1 { get; set; }
public int Key2 { get; set; }
}
The serialization methods:
public static class MyDataListSerializer
{
public static JsonValue ToJson(List<MyData> data, JsonSerializer serializer)
{
return new JsonArray
{
new JsonArray {"Key1", "Key2"},
new JsonArray(data.Select(d => d.Key1)),
new JsonArray(data.Select(d => d.Key2)),
};
}
public static MyData FromJson(JsonValue value, JsonSerializer serializer)
{
return value.Array.Skip(1)
.Array.Select((jv, i) => new MyData
{
Key1 = (int) jv.Number,
Key2 = value.Array[2].Array[i]
};
}
}
Registering the methods:
JsonSerializationTypeRegistry.RegisterType(MyDataSerializer.ToJson,
MyDataSerializer.FromJson);
And finally the deserialize methods. I'm not sure what your method signatures are, but you mentioned that you're receiving a stream for the deserialize, so I'll start with that.
public string Serialize(MyData data)
{
// _serializer is an instance field of type JsonSerializer
return _serializer.Serialize(data).ToString();
}
public MyData Deserialize(Stream stream)
{
var json = JsonValue.Parse(stream);
return _serializer.Deserialize<MyData>(json);
}
This approach forces the static serializer methods to handle the formatting of the JSON. There is no real transformation occurring here; it's serializing directly to and from the desired format.
Edit 3
Hopefully this is the last edit. This answer is becoming a dissertation.
I couldn't live with myself not having a translation solution. However, working out the serialization piece led me to the answer. There was ambiguity in how the transformer interpreted the paths in that array special case, so I split it up.
JsonPath specifies an alternate root symbol when looking at items within an array: @. This convention is now adopted in the transformer as well.
The original transform template becomes:
[["Key1","Key2"],["$[*]","@.Key1"],["$[*]","@.Key2"]]
This allows us to create a reverse template:
[
"$[1][*]", // Get all of the items in the first value list
{
"Key1":"@", // Key1 is sourced from the item returned by '$[1][*]'
"Key2":"$[2][*]" // Key2 is sourced from the items in the second element
// of the original source (not the item returned by '$[1][*]')
}
]
Now you can transform both directions and you don't have to do anything fancy with the custom serialize methods.
The serializer will look something like this now:
public string Serialize(MyData data)
{
// _serializer is an instance field of type JsonSerializer
var json = _serializer.Serialize(data);
// _transformTemplate is an instance field of type JsonValue
// representing the first template from above.
var transformedJson = json.Transform(_transformTemplate);
return transformedJson.ToString();
}
public MyData Deserialize(Stream stream)
{
var json = JsonValue.Parse(stream);
// _reverseTransformTemplate is an instance field of type JsonValue
// representing the second template from above.
var untransformedJson = json.Transform(_reverseTransformTemplate);
return _serializer.Deserialize<MyData>(untransformedJson);
}
[JsonProperty("$")] public string LongPropertyName {set;get;}List<Dictionary<string,int>>). So you can only serialize the values (without keys) (List<List<int>>)..