Custom Json Serializer for deep nested objects
Your "goal" JSON is tricky to handle because the treatment of the SubDataMappers
list is different depending on whether the children have a non-null DataMapperProperty
or a non-empty list of SubDataMappers
. In the former case, you want it rendered as an object containing one property per child DataMapper
; in the latter, as an array of objects containing one DataMapper
each. Also, I see you are using the Name
property of the DataMapper
as a key in the JSON rather than as the value of a well-known property. Given these two constraints, I think the best plan of attack is to make a JsonConverter
that operates on a list of DataMappers
rather than a single instance. Otherwise, the converter code is going to get pretty messy. If that is acceptable, then the following converter should give you what you want:
public class DataMapperListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<DataMapper>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<DataMapper> list = (List<DataMapper>)value;
if (list.Any(dm => dm.DataMapperProperty != null))
{
JObject obj = new JObject(list.Select(dm =>
{
JToken val;
if (dm.DataMapperProperty != null)
val = JToken.FromObject(dm.DataMapperProperty, serializer);
else
val = JToken.FromObject(dm.SubDataMappers, serializer);
return new JProperty(dm.Name, val);
}));
obj.WriteTo(writer);
}
else
{
serializer.Serialize(writer,
list.Select(dm => new Dictionary<string, List<DataMapper>>
{
{ dm.Name, dm.SubDataMappers }
}));
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.Children<JProperty>()
.Select(jp =>
{
DataMapper mapper = new DataMapper { Name = jp.Name };
JToken val = jp.Value;
if (val["data-type"] != null)
mapper.DataMapperProperty = jp.Value.ToObject<DataMapperProperty>(serializer);
else
mapper.SubDataMappers = jp.Value.ToObject<List<DataMapper>>(serializer);
return mapper;
})
.ToList();
}
else if (token.Type == JTokenType.Array)
{
return token.Children<JObject>()
.SelectMany(jo => jo.Properties())
.Select(jp => new DataMapper
{
Name = jp.Name,
SubDataMappers = jp.Value.ToObject<List<DataMapper>>(serializer)
})
.ToList();
}
else
{
throw new JsonException("Unexpected token type: " + token.Type.ToString());
}
}
}
Assumptions:
- You will never be serializing a single
DataMapper
by itself; it will always be contained in a list. DataMappers
can be nested to an arbitrary depth.- A
DataMapper
will always have a non-nullName
, which is unique at each level. - A
DataMapper
will never have both a non-nullDataMapperProperty
and a non-empty list ofSubDataMappers
. - A
DataMapperProperty
will always have a non-nullDataType
. - A
DataMapper
will never have aName
ofdata-type
.
If the last four assumptions do not hold true, then this JSON format will not work for what you are trying to do, and you will need to rethink.
To use the converter, you will need to add it to your serializer settings as shown below. Use the settings both when you serialize and deserialize. Remove the [JsonConverter]
attribute from the DataMapper
class.
var settings = new JsonSerializerSettings()
{
Converters = new List<JsonConverter> { new DataMapperListConverter() },
Formatting = Formatting.Indented
};
Here is a round-trip demo: https://dotnetfiddle.net/8KycXB
JS Creating a custom json by a nested json
Please replace your code with below one, it will work straight away.
Key will be "state-1", "state-2" instead of "0", "1"
function transform(){
let items = [
{
"carId":328288,
"firstName":"yathindra",
"lastName":"rawya",
"list":[
{
"id":182396,
"isAvail":false,
"stateId":288,
"state":"Awesome"
},
{
"id":182396,
"isAvail":false,
"stateId":678,
"state":"Cool1"
}
],
},
{
"carId":3282488,
"firstName":"yathindraR",
"lastName":"K",
"list":[
{
"id":18232396,
"isAvail":false,
"stateId":22388,
"state":"Awesome"
},
{
"id":182356796,
"isAvail":false,
"stateId":45678,
"state":"Cool"
}
],
}
]
let customList = [];
for(let i=0;i<items.length;i++){
let temp = {};
for(let j=0;j<items[i].list.length;j++){
temp["state-"+(j+1)] = items[i].list[j].state;
}
customList.push({
fname: items[i].firstName,
lname: items[i].lastName,
...temp
})
}
console.log(JSON.stringify(customList))
}
transform();
Gson - Serialize Nested Object as Attributes
Update I looked into GsonBuilder and yes you can do it with custom serialization. You need to override serialize
method of JsonSerializer<type>
Just define a class as below. here only 2 properties are added.
public class FooSerialize implements JsonSerializer<foo> {
@Override
public JsonElement serialize(foo obj, Type foo, JsonSerializationContext context) {
JsonObject object = new JsonObject();
String otherValue = obj.b.other;
object.addProperty("other", otherValue );
object.addProperty("text", obj.text);
return object;
}
}
Create gson
object as below.
Gson gson = new GsonBuilder().registerTypeAdapter(foo.class, new FooSerialize()).setPrettyPrinting().create();
Just convert to Json
gson.toJson(fooObject);
Voila! lmk if it works for you. I tested on my system it worked. Forget about to string override it gets called for Json to Obj conversion. This is only serialization you need to handle deserialize to object as well. Look for online resources to get an idea on similar line.
Alternate solution would be define dummy pojos only for JSON conversion purposes. While sending use setters to assign values to pojo object and use gson on pojo vice versa or above solution to have custom serialize and deserialize for class you need.
Deserialization with nested object and in-between step
I recommend:
- a dedicated Dto; or
[JsonIgnore]
onLicense
and an auxiliary field;
For example:
public class LicenseFile
{
[JsonIgnore]
public License License { get; set; }
public string LicenseJson => JsonSerializer.Serialize(License); // Add base64 encoding if you wish
public byte[] Signature { get; set; }
}
or
public class LicenseFileDto
{
public LicenseFileDto(LicenseFile license) {
License = JsonSerializer.Serialize(license.License);
Signature = license.Signature;
}
public string License { get; }
public byte[] Signature { get; }
}
Test
var l = new LicenseFile
{
License = new License(name: "X Y", validUntil: new DateTime(2021, 04, 22)),
Signature = new byte[] { 1, 2, 3 }
};
var json = JsonSerializer.Serialize(l,
options: new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
var dtoJson = JsonSerializer.Serialize(new LicenseFileDto(l),
options: new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(dtoJson);
// Documentation at:
// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-character-encoding#serialize-all-characters
// says:
// Use the unsafe encoder only when it's known that the client will be interpreting the resulting payload as UTF-8 encoded JSON.
var prettyJson = JsonSerializer.Serialize(new LicenseFileDto(l),
options: new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
Console.WriteLine(prettyJson);
{
"LicenseJson": "{\u0022Name\u0022:\u0022X Y\u0022,\u0022ValidUntil\u0022:\u00222021-04-22T00:00:00\u0022}",
"Signature": "AQID"
}
{
"License": "{\u0022Name\u0022:\u0022X Y\u0022,\u0022ValidUntil\u0022:\u00222021-04-22T00:00:00\u0022}",
"Signature": "AQID"
}
{
"License": "{\"Name\":\"X Y\",\"ValidUntil\":\"2021-04-22T00:00:00\"}",
"Signature": "AQID"
}
Serialize flat object to nested JSON structure
Where it says // how to write the nested structure, have you tried something like
jgen.writeObjectFieldStart("Other")
jgen.writeNumberField("Nested.BusinessName", value.prop2)
jgen.writeEndObject()
Django serialise nested object into json
ser = ForumSubSectionSerializer(qs, many=True)
already serializes the data for you. All you need to do is: get the data from it with ser.data
or you may access the data
directly from the class ForumSubSectionSerializer(qs, many=True).data
.
def ajax_edit_forum_subsection(request):
qs = ForumSubSection.objects.all().order_by('pk')
ser = ForumSubSectionSerializer(qs, many=True).data # <- data here
return JsonResponse(ser, safe=False)
Notice the many=Trye
parameter. If a representation should be a list of items, you should pass the many=True
flag to the serializer.
Related Topics
How to Generate a Human Readable Time Range Using Ruby on Rails
How to Count the Number of Records That Have a Unique Value in a Particular Field in Ror
Looping Through an Array with Step
Does Ruby Provide a Way to Do File.Read() with Specified Encoding
Peer-To-Peer File Sharing with Web Sockets
Ruby and "You Must Recompile Ruby with Openssl Support or Change the Sources in Your Gemfile"
Warning: Can't Verify Csrf Token Authenticity in Case of API Development
Rails Forms for Has_Many Through Association with Additional Attributes
Differencebetween 'After_Create' and 'After_Save' and When to Use Which
Differencebetween Using .Exists, and .Present? in Ruby
Escaping Single and Double Quotes in a String in Ruby
Why Does Array.Slice Behave Differently for (Length, N)
Irb History Not Working with Ruby 2.3.0
Activerecord::Adapternotspecified Database Configuration Does Not Specify Adapter
Is There Goto Statement in Ruby
Where's the Best Place to Define a Constant in a Ruby on Rails Application