5

Edit: I guess I should mention that I do not have control over the JSON and I know that typically my C# object should match the JSON. My question wasn't "why isn't this deserializing?". I know why it's not. I am asking if there is a way to deserialize the JSON the way that I am asking.

I am using Newtonsoft.Json.

I have a JSON string containing 1 object. I need to deserialize that object into a C# object with a nested object.

So lets say my JSON looks like this.

{
    "id": 123,
    "userName": "fflintstone",
    "address": "345 Cave Stone Road",
    "address2": "",
    "city": "Bedrock",
    "state": "AZ",
    "zip": "",   
}

And here is my C# object

public class Customer
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public AddressModel Address { get; set; }
}

The AddressModel Address property is a nested object. That object contains the actual address properties. So I need to deserialze my JSON object so that id and userName are added to the Customer object and then the address fields are added to the nested Address object.

I haven't been able to find a way that is built into newtonsoft to accomplish this. Any ideas?

1
  • 3
    Your C# object is not a representation of your JSON though... Your JSON object is an object with 7 properties (id, userName, address, address2, city, state, zip). Your Customer class needs to have those same properties Commented Jun 25, 2018 at 18:35

5 Answers 5

7

You can do this using a custom JsonConverter.

public class CustomerJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is Customer customer)
        {
            var token = new JObject
            {
                ["id"] = customer.Id,
                ["userName"] = customer.UserName,
                ["address"] = customer.Address.Address,
                ["address2"] = customer.Address.Address2,
                ["city"] = customer.Address.City,
                ["state"] = customer.Address.State,
                ["zip"] = customer.Address.ZIP
            };

            token.WriteTo(writer);
        }
        else
        {
            throw new InvalidOperationException();
        }

    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = JToken.ReadFrom(reader);

        if (obj.Type != JTokenType.Object)
        {
            return null;
        }

        return new Customer
        {
            Id = (long) obj["id"],
            UserName = (string) obj["userName"],
            Address = obj.ToObject<AddressModel>()
        };
    }

    public override bool CanRead => true;

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Customer);
    }
}

Along with a JsonConverterAttribute on the Customer class.

[JsonConverter(typeof(CustomerJsonConverter))]
public class Customer
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public AddressModel Address { get; set; }
}

public class AddressModel
{
    public string Address { get; set; }

    public string Address2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string ZIP { get; set; }
}

And utilize as such:

var customer = JsonConvert.DeserializeObject<Customer>(customerJson);

alternatively, you could simply have an intermediate mapping model.

public class CustomerFlattened
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public string Address { get; set; }

    public string Address2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string ZIP { get; set; }

    public Customer ToCustomer()
    {
        return new Customer
        {
            Id = Id,
            UserName = UserName,
            Address = new AddressModel
            {
                Address = Address,
                Address2 = Address2,
                City = City,
                State = State,
                ZIP = ZIP
            }
        };
    }

    public static CustomerFlattened FromCustomer(Customer customer)
    {
        return new CustomerFlattened
        {
            Id = customer.Id,
            UserName = customer.UserName,
            Address = customer.Address.Address,
            Address2 = customer.Address.Address2,
            City = customer.Address.City,
            State = customer.Address.State,
            ZIP = customer.Address.ZIP
        };
    }
}

And utilize as such:

var customer =
    JsonConvert.Deserialize<CustomerFlattened>(
        jsonOriginal
    )
    .ToCustomer();

var customerFlattened = CustomerFlattened.FromCustomer(customer);

var jsonConverted = JsonConvert.Serialize(customerFlattened );
Sign up to request clarification or add additional context in comments.

1 Comment

This guy gets it.
3

If you're the one setting up the JSON that'll be consumed by your code, then your JSON is not setup to match your object.

It should look like this:

{
    "id": 123,
    "userName": "fflintstone",
    "address": {
        // address properties here
    }
} 

Otherwise you need to update your C# object to match the JSON, which would mean individual properties for each item in the JSON:

public class Customer
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public string Address { get; set; }

    public string Address2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string ZIP { get; set; }
}

Further, if you're setting up both of these, the street name probably shouldn't be named "Address". To me, the word "address" means the street name, number, city, state, and ZIP as one whole thing.

If you don't have control over the JSON, then there's no real way to cleanly deserialize the JSON into your object with something like Json.Net. You'll need to set up some sort of mapper, or look for the properties directly to obtain their values to add them to your object. You can parse the JSON into a JObject, then access the properties you need.

JObject foo = JObject.Parse(//your JSON string here);
customer.Address = (string)foo["Address"];
customer.Address2 = (string)foo["Address2];

In general, if you're not the one in control of the JSON structure, it's probably best to just make your objects match the JSON you're given.

2 Comments

While this is probably the "right" answer. It does not answer my question. I know how to make it deserialize "normally". I'm wondering if there is a way to deserialize it in the way that I asked. And maybe there isn't. But that's why I am asking; to find out if there is.
A detail you added after I was already typing/editing my answer. Updating it now. :)
1

If you're willing to use an additional tool, you can create a DTO that matches the JSON and then use a mapper like Automapper to reverse flatten the DTO into your object model.

3 Comments

I still don't get why people insist on using a tool which can be replaced by a simple static method.
@cwharris No one is insisting on anything. Answers create exposure to options. GG tho
Dunno what “GG” means in this context. And yes, some people insist that automapper is the right tool for the job when converting one object to another, but I just don’t see how it’s useful.
1

Since your JSON and your object model don't match, you'll have to deserialize to a temporary object and map the fields yourself. In this example, I deserialize to an anonymous object which is used like a template.

//This is our test data
var input = @"{
    ""id"": 123,
    ""userName"": ""fflintstone"",
    ""address"": ""345 Cave Stone Road"",
    ""address2"": """",
    ""city"": ""Bedrock"",
    ""state"": ""AZ"",
    ""zip"": """"   
}";

//This anonymous type will be used as a template to deserialize the test data
var template = new 
{ 
    id = default(int),
    userName = default(string),
    address = default(string),
    address2 = default(string),
    city = default(string),
    state = default(string),
    zip = default(string)
};

//Deserialize
var temp = JsonConvert.DeserializeAnonymousType(input, template);

//Use the deserialized object to create an AddressModel
var address = new AddressModel
{
    Address = temp.address,
    Address2 = temp.address,
    City = temp.city,
    State = temp.state,
    Zip = temp.zip
};

//Create a customer using the deserialized data and our new AddressModel instance
var customer = new Customer
{
    Id = temp.id,
    UserName = temp.userName,
    Address = address
};

//Output a couple fields to check
Console.WriteLine("{0} {1}", customer.Id, customer.Address.City);

Output:

123 Bedrock

Working example on DotNetFiddle

Comments

-1

First for the object you want to create with AdressModel class you need to change your JSON document, I have changed to:

  {   "id": 123,   "userName": "fflintstone",   "Address": {
"address": "345 Cave Stone Road",
"address2": "",
"city": "Bedrock",
"state": "AZ",
"zip": ""   } }

Then for models I have created this two models:

public class Customer
{
    public long id { get; set; }
    public string userName { get; set; }
    public AddressModel Address { get; set; }
}

public class AddressModel
{
    public string address { get; set; }
    public string address2 { get; set; }
    public string city { get; set; }
    public string state { get; set; }
    public string zip { get; set; }
}

Now to Deserialize the Json Document you could do something like this:

Customer jsonConverted = new Customer();

        using (StreamReader r = new StreamReader(HostingEnvironment.ApplicationPhysicalPath + @"\infoFile.json"))
        {
            var json = r.ReadToEnd();
            jsonConverted = JsonConvert.DeserializeObject<Customer>(json);
        }

(I'm testing with a MVC project) enter image description here

2 Comments

Asker has indicated they're not in control of the JSON.
Sorry, the question was edited while I was writing the answer. Anyway this answer can be taken as an example

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.