9

I need to generate test cases (using IEnumerable<TestCaseData>) from JSON objects that have values set. I couldn't find any tools online that would generate C# classes that have values set (Plenty that generate classes). Deserializing Json in test cases would invalidate test (One should only be testing the code), so I did this regex.

It's not perfect but worked just fine with most basic well formatted JSON when all properties are of same type (decimals or bools), pattern: "([a-zA-Z]?[a-zA-Z0-9].*)" ?: ?((true|false|null)|([\d].*)) , replace $1 = $2M however when there are many types and they are mixed I get screwed.

I am sure someone ran into this before me and I am reinventing wheel so in a nutshell.

How do I make this:

{
    "LegalFeeNet": 363.54,
    "LegalFeeVat": 72.708,
    "DiscountNet": 0.0,
    "DiscountVat": 0.0,
    "OtherNet": 12.0,
    "OtherVat": 2.4,
    "DisbursementNet": 220.0,
    "DisbursementVat": 0.0,
    "AmlCheck": null,
    "LegalSubTotal": 363.54,
    "TotalFee": 450.648,
    "Discounts": 0.0,
    "Vat": 75.108,
    "DiscountedPrice": 360.5184,
    "RecommendedRetailPrice": 450.648,
    "SubTotal": 375.54,
    "Name": "Will",
    "IsDiscounted": false,
    "CustomerCount": 3
}

to become this:

ClassName {
    LegalFeeNet = 363.54M,
    LegalFeeVat = 72.708M,
    DiscountNet = 0.0M,
    DiscountVat = 0.0M,
    OtherNet = 12.0M,
    OtherVat = 2.4M,
    DisbursementNet = 220.0M,
    DisbursementVat = 0.0M,
    AmlCheck = nullM,
    LegalSubTotal = 363.54M,
    TotalFee = 450.648M,
    Discounts = 0.0M,
    Vat = 75.108M,
    DiscountedPrice = 360.5184M,
    RecommendedRetailPrice = 450.648M,
    SubTotal = 375.54M,
    Name = "Will",
    IsDiscounted = false,
    CustomerCount = 3
}

What fastest/most convenient solution is to generate C# classes that have properties set from JSON objects?

Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
  • 3
    I would personally suggest you simply become less dogmatic about the claim that it would invalidate the test. Which part of the code that would get executed if you parsed the JSON instead of using an object initializer do you object to? – Jon Skeet Dec 07 '15 at 11:59
  • _"Deserializing Json in test cases would invalidate test"_ - do you want to be pure, or pragmatic? Anyway depending on your test harness (MSTest / NUnit), you can decorate your tests with an attribute that deserializes the file and instantiates your test data. I mean if you want to do the "JSON to C# object initializer" yourself (like you started with this regex), you're going to have to accommodate for non-C# identifiers (JSON property names with dashes and such), nesting, different variable types and so on. – CodeCaster Dec 07 '15 at 11:59
  • @JonSkeet TBH I have my tests written this way (long json string that gets deserialised), I had this 'itching' for quite some time now, my biggest problem is that they are pain to change values when structures change (and in general to work with them I usually copy them over to jsbeautifier then parse -> change values -> collapse and copy them back). I just thought I was doing this 'backwards' all this time and world has moved on and there are plenty of solutions to do this, and I am the only guy left that is replacing values with regexes and such. Invalidating tests was more to convince self. – Matas Vaitkevicius Dec 07 '15 at 12:15
  • Well when stuff changes, you'll need to change *something*. With object initializers you'd end up with a simple rename within Visual Studio, but if you need to add or remove things you'll have similar problems either way. – Jon Skeet Dec 07 '15 at 12:16
  • You could use a different JSON serializer to create your test cases than you are testing for serialization. For instance, create your `IEnumerable` with `JavaScriptSerializer` or `DataContractJsonSerializer` for testing Json.NET, and vice versa. – dbc Dec 07 '15 at 21:31

2 Answers2

7

I was also here in search of a solution to the same problem.

The accepted answer missed some features I wanted, so ended up creating this https://jsontocsharpconverter.web.app/

Hopefully.. it helps someone.

hybrid
  • 1,255
  • 2
  • 17
  • 42
  • Hi, I have accepted your answer since a nice dedicated page beats my ugly script hundredfold. I would really appreciate it if you could add one feature when making nested objects (`"Obj1": { "One": 1, "Dec": 1.1, "Str": "Stringer", "Bolie": true, "Obj2": { "One": 1, "Dec": 1.1,`) page produces `Obj1 = Obj1{ One = 1, Dec = 1.1, Str = "Stringer", Bolie = true, Obj2 = Obj2{ One = 1, Dec = 1.1,` I think `new` keyword would be nice before `Obj1` and `Obj2`. And again thanks for solution. – Matas Vaitkevicius Aug 28 '20 at 16:38
  • 1
    Thanks for the feedback. I have added the new in front of nested – hybrid Aug 29 '20 at 02:20
  • @hybrid You are a hero! Thank you! – Neil Bostrom Nov 30 '20 at 10:53
2

So I have failed to find any out of the box solution - had to write my own.

Script below can be used as converter, it probably is full of bugs. Still it worked for everything I needed to do so far.

function Convert(jsonStr, classNr) {
  var i = classNr == undefined ? 0 : classNr;
  var str = "";
  var json = JSON.parse(jsonStr);
  for (var prop in json) {
    if (typeof(json[prop]) === "number") {
      if (json[prop] === +json[prop] && json[prop] !== (json[prop] | 0)) {
        str += prop + " = " + json[prop] + "M, ";
      } else {
        str += prop + " = " + json[prop] + ", ";
      }
    } else if (typeof(json[prop]) === "boolean") {
      str += prop + " = " + json[prop] + ", ";
    } else if (typeof(json[prop]) === "string") {
      str += prop + ' = "' + json[prop] + '", ';
    } else if (json[prop] == null || json[prop] == undefined) {
      str += prop + ' = null, ';
    } else if (typeof(json[prop]) === "object") {
      str += prop + " = " + Convert(JSON.stringify(json[prop]), i++) + ", ";
    }
  }
  return "new Class" + i + "{ " + str + " }";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<textarea cols="100" rows="6">
  { "StingProperty" : "StringVal", "LegalFeeNet": 363.54, "LegalFeeVat": 72.708, "DiscountNet": 0.0, "DiscountVat": 0.0, "OtherNet": 12.0, "OtherVat": 2.4, "DisbursementNet": 220.0, "DisbursementVat": 0.0, "AmlCheck": null, "LegalSubTotal": 363.54, "TotalFee":
  450.648, "Discounts": 0.0, "Vat": 75.108, "DiscountedPrice": 360.5184, "RecommendedRetailPrice": 450.648, "SubTotal": 375.54, "Name": "Will", "IsDiscounted": false, "CustomerCount": 3, "Obj" : {"One" : 1, "Dec" : 1.1, "Str" : "Stringer", "Bolie" : true},"Obj1"
  : {"One" : 1, "Dec" : 1.1, "Str" : "Stringer", "Bolie" : true, "Obj2" : {"One" : 1, "Dec" : 1.1, "Str" : "Stringer", "Bolie" : true}} }
</textarea>
<input type="button" value="Just do it!" onclick="$('#result').append(Convert($('textarea').text()));" />
<div id="result"></div>
Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265