Zach M.
Zach M.

Reputation: 1194

Knockout json mapping error

I have read a few posts on the matter but wanted to ask as my own code differs slightly. I am developing a golf game tracking website and have run into an issue

I have the following View:

@{
    ViewBag.Title = "Games";
}

<h2>Games</h2>
<table class="table-bordered">
    <thead>
        <tr>
            <th>Course</th>
            <th>Date</th>
            <th>Player</th>
            <th>Hole 1</th>
            <th>Hole 2</th>
            <th>Hole 3</th>
            <th>Hole 4</th>
            <th>Hole 5</th>
            <th>Hole 6</th>
            <th>Hole 7</th>
            <th>Hole 8</th>
            <th>Hole 9</th>
        </tr>
    </thead>
    <tbody data-bind="foreach: GameList">
        <tr>
            <td data-bind="text: CourseName"></td>
            <td data-bind="text: Date"></td>
        </tr>
        <!-- ko foreach: GameEntry -->
        <tr>
            <td></td>
            <td></td>
            <td data-bind="text:PlayerName"></td>
            <td data-bind="text:HoleOneScore"></td>
            <td data-bind="text:HoleTwoScore"></td>
            <td data-bind="text:HoleThreeScore"></td>
            <td data-bind="text:HoleFourScore"></td>
            <td data-bind="text:HoleFiveScore"></td>
            <td data-bind="text:HoleSixScore"></td>
            <td data-bind="text:HoleSevenScore"></td>
            <td data-bind="text:HoleEightScore"></td>
            <td data-bind="text:HoleNineScore"></td>
        </tr>
        <!-- /ko -->
    </tbody>
</table>
<script>
    function Game(data){
        var self = this;
        self.CourseName = ko.observable(data.CourseName);
        self.Date = ko.observable(data.Date);
        self.GameEntries = ko.observableArray(new GameEntry(data.GameEntries));
    }

    function GameEntry(data){
        var self = this;
        self.PlayerName = ko.observable(data.PlayerName);
        self.HoleOneScore = ko.observable(data.HoleOneScore);
        self.HoleTwoScore = ko.observable(data.HoleTwoScore);
        self.HoleThreeScore = ko.observable(data.HoleThreeScore);
        self.HoleFourScore = ko.observable(data.HoleFourScore);
        self.HoleFiveScore = ko.observable(data.HoleFiveScore);
        self.HoleSixScore = ko.observable(data.HoleSixScore);
        self.HoleSevenScore = ko.observable(data.HoleSevenScore);
        self.HoleEightScore = ko.observable(data.HoleEightScore);
        self.HoleNineScore = ko.observable(data.HoleNineScore);
    }

    function ViewModel() {
        var self = this;
        self.GameList = ko.observableArray([]);

        $.getJSON("/Games/GetGames", function (allData) {
            var mappedTasks = $.map(allData, function (item) { return new Game(item) });
            self.GameList(mappedTasks);
        });
    }

    $(document).ready(function () {       
        ko.applyBindings(new ViewModel());
    });
</script>

Which calls this controller function:

[HttpGet]
        public JsonResult GetGames()
        {
            return Json(JsonConvert.SerializeObject(GameManager.GetGames(), Formatting.Indented), JsonRequestBehavior.AllowGet);
        }

Which has the following Model and mapping:

public static List<GameModel> GetGames()
        {
            var gameList = new List<GameModel>();
            using (var context = new Entities())
            {
                var games = context.Games.ToList();
                foreach (var game in games)
                {
                    var model = new GameModel();
                    model.MapGame(game);
                    gameList.Add(model);
                }                
            }
            return gameList;
        }

public class GameModel
    {
        public string CourseName { get; set; }
        public DateTime Date { get; set; }

        public List<PlayerGameEntry> GameEntries { get; set; }

        public GameModel()
        {
            GameEntries = new List<PlayerGameEntry>();
        }

        public class PlayerGameEntry
        {
            public string PlayerName { get; set; }
            public int? HoleOneScore { get; set; }
            public int? HoleTwoScore { get; set; }
            public int? HoleThreeScore { get; set; }
            public int? HoleFourScore { get; set; }
            public int? HoleFiveScore { get; set; }
            public int? HoleSixScore { get; set; }
            public int? HoleSevenScore { get; set; }
            public int? HoleEightScore { get; set; }
            public int? HoleNineScore { get; set; }
        }

        public void MapGame(Game game)
        {

            CourseName = game.Course.CourseName;
            Date = game.Date;           

            foreach (var entry in game.GameEntries)
            {
               GameEntries.Add(new PlayerGameEntry
                {
                    PlayerName = entry.Golfer.PlayerName,
                    HoleOneScore = entry.HoleOneScore,
                    HoleTwoScore = entry.HoleTwoScore,
                    HoleThreeScore = entry.HoleThreeScore,
                    HoleFourScore = entry.HoleFourScore,
                    HoleFiveScore = entry.HoleFiveScore,
                    HoleSixScore = entry.HoleSixScore,
                    HoleSevenScore = entry.HoleSevenScore,
                    HoleEightScore = entry.HoleEightScore,
                    HoleNineScore = entry.HoleNineScore
                });
            }
        }
    }

I am receiving this error in my browser:

Uncaught TypeError: Cannot use 'in' operator to search for '967' in [
  {
    "CourseName": "Green Hills",
    "Date": "2013-04-02T16:33:21.943",
    "GameEntries": [
      {
        "PlayerName": "Chris Camp",
        "HoleOneScore": 4,
        "HoleTwoScore": 5,
        "HoleThreeScore": 6,
        "HoleFourScore": 3,
        "HoleFiveScore": 4,
        "HoleSixScore": 4,
        "HoleSevenScore": 4,
        "HoleEightScore": 5,
        "HoleNineScore": 3
      }
    ]
  },
  {
    "CourseName": "Green Hills",
    "Date": "2015-05-01T13:08:41.783",
    "GameEntries": []
  },
  {
    "CourseName": "Green Hills",
    "Date": "2015-05-01T13:13:45.34",
    "GameEntries": [
      {
        "PlayerName": "Chris Camp",
        "HoleOneScore": 4,
        "HoleTwoScore": 3,
        "HoleThreeScore": 4,
        "HoleFourScore": 7,
        "HoleFiveScore": 5,
        "HoleSixScore": 2,
        "HoleSevenScore": 1,
        "HoleEightScore": 4,
        "HoleNineScore": 6
      }
    ]
  }
]

I thought that the binding within the getJson would handle mapping all of the fields that I have set based on my observables, is there anything that I am just outright not understanding about how knockout binds this data? I have done this is the past with just a single mapped object but not one with a sub object.

Upvotes: 0

Views: 56

Answers (1)

Artem
Artem

Reputation: 3700

Your problem is that you first serialize some array to JSON with JsonConvert, and then pass string to MVC Json method, which will try to serialize this string to JSON again. In this case data variable in your getJSON callback will be of type string, which will contain your json (it will be string of length 967 characters, thats why you see this exception).

You need to replace code in your controller with:

[HttpGet]
public JsonResult GetGames()
{
  return Json(GameManager.GetGames(), JsonRequestBehavior.AllowGet);
}

Upvotes: 1

Related Questions